In this post I’m going to cover the default/general architectural structure that I use when building a UI application.
Application Architecture Composition
When designing the architecture of an application I like to break the package down into components. Each component is responsible for providing an interface to a set of cohesive features that can be loosely coupled with other components to provide the full set of features required by the application.
As a general rule and default starting place for a GUI application, the primary components will be an implementation of the MVC architectural pattern. This decision encourages the notion that there should be at least three components in our application: Model
, View
and Controller
. In order for these components to be loosely coupled, the application will likely need a few services such as a message broker and a dependency injection service. So I’ll add another component called Service
. All applications need to have an entry point into the package, so I’ll add one more component to the solution that will be called UI
as this is the component that is responsible for providing users access to the application.
Within the Model
component, there are two subcomponents: Command
and Event
. The Model.Command
component isolates data structures used to represent user commands sent from objects in the View
to objects in the Controller
. The Model.Event
component isolates data structures used to represent events raised by objects in the Model
changing state.
Below is a UML component diagram that shows the dependency chain of these components. I used UMLetino to create the diagram.
The Model
and the Service
components are the most important since all of the other components depend on them. Also notice that neither the Model
nor the Service
components depend on any thing.
Model Component
The Model
component is responsible for providing the state of the application and encapsulating the logic the application is providing the user the ability to work with. It is also responsible for declaring Command and Event objects that are used to model the behavior the application is modeling. For example, if the application was supporting an Accounts Receivable Clerk’s ability to create invoices, then there would likely be an Account
object in the Model
component, a GenerateInvoice
object in the Command
component and an InvoiceGenerated
object in the Event
component.
Most applications will require some sort of user security where a user must authenticate their credentials to prove their identity to the system and once their identity is recognized, the system will grant them authorization to access and potentially manipulate resources controlled by the system.
If the application requires tracking the state of a user, they could be modeled in a Session
object. An active Session
would have properties such as an Identity
representing the user’s information such as name, title, contact information, etc.; and a set of authorization Claims
representing the authorizations granted the user. It would also provide a method to end the Session
. The commands and events corresponding to the Session would include objects such as AuthenticateCredentials
, AuthenticationFailed
, SessionInitialized
, SessionStarted
, and SessionEnded
.
View Component
The View
component is responsible for providing a visual representation of the state of the objects in the Model
component. Objects in the View
component will use a message broker to subscribe to one or more Model.Event
(s) in order to be notified when a Model
object has changed its state.
Objects defined in the View
component are responsible for translating user interactions into the appropriate Model.Command
objects and publishing those commands via a message broker. Typically, applications display a nameplate/masthead that display the organization’s logo, controls for accessing the user’s profile, as well as the ability to logout, and a menu of links to resources/features of the application. These nameplates come in many styles and each system will require the application’s display stylistically match the organization’s branding. To design an application with this minimal feature, we would need to include Masthead
, Logo
, Profile
, and IdentityChallenge
objects in the View
component.
The Masthead
would subscribe to the SessionInitialized
event and would handle receiving notification of that event by checking if the Session
that was initialized is currently active and if it is, render the Logo
, Profile
and itself. If the Session
isn’t active, then the Masthead
would remove the Profile
from view and potentially even change the style of the Logo
to be larger and/or in a different position.
Similarly, the IdentityChallenge
would be subscribed to the SessionInitialized
event and would check if the initialized Session
was active. If it is active, the IdentityChallenge
would remove itself from the UI as it is not required. If it isn’t active, it would append itself to the UI in order to allow the user to authenticate/start a new Session
.
Controller Component
The Controller
component is responsible for controlling the state of objects in the Model
. Objects in the Controller
component will use a message broker to subscribe to one or more Model.Command
(s) in order to be notified when a View
object has been interacted with by a user. Objects defined in the Controller
component are responsible for calling the appropriate methods on a Model
object based on the received Command
and publishing the results as a Model.Event
object. In some cases, the Model
object’s method that corresponds with the received Command
will require data that is external to the application. In those cases, the Controller
object will use a service to make a call to the external data source and use the results of that request to fulfill the Command
.
As an example, if the Profile
object in the View
component contains a button for logging out, when the user clicks that button the Profile
will publish a new EndSession
command. The SessionController
object will be subscribed to that event and be notified. It will then send an HTTP request to the identity and access management API (examples are Auth0 or Google’s Firebase Auth) to end the user’s session. When it receives a success response it will call the End()
method on the Session
object.
Service Component
The Service component is responsible for providing application services to the other components. These services include a dependency injection container and a pub/sub message broker.
Objects defined in the Service
component are responsible for handling the infrastructure duties required by any application to function in its host environment.
UI Component
The UI component is responsible for providing the entry point of the application. In the case of a browser-based application, the host environment is a browser DOM’s window object. When the window loads, it will fire a load event. The UI component makes use of the EventTarget
interface and adds an EventListener
which will use the Service
‘s dependency injection Container
to initialize the application based on configuration settings. Once the application is initialized, it will publish a SessionInitialized
event via the MessageBroker
. View
objects subscribed to the SessionInitialized
event will render the appropriate user interface and enable the application to respond to the user’s interactions as described in the View Component section above.
Next Steps
With the basic architectural guidelines defined, when a project needs a user interface application, this architecture can be used to provide a launching pad for defining the product requirements that a developer will need to implement. The next post in this series will be about defining these requirements for a fictional system.
[…] my previous post, I described a default architecture for a UI application. In this post, I will introduce the […]
LikeLike