Web Applications
Last updated
Last updated
Web applications are the most common type of exposer components today. They are much more easier to use than any other known exposer UI component in the software industry. But more importantly, web applications have much more divrse set of technologies than mobile applications. The web software market is also much easier for engineer to publish to and update than mobile applications which makes it quite attractive for newer engineers in general.
In this chapter, we will be using Blazor technology to demonstrate implementing The Standard principles for web applications. But as I previously mentioned, The Standard is technology-agnostic. Which means it can be applied to any web technology without any issues.
Web applications usually set at the other end of any system. They are the terminals that humans use to interact with the system. Let's take a look at where they are located on the map:
As shown above, web applications are somewhat similar to core APIs, except that they have a different group of components in terms of visualization such as Pages, Components and Bases. There's an intersection between two main flows in every web application. The presentation flow and the data/business flow. Depending on where a web application lives in terms of high-level architecture it's location determines whether it's backend (BFF or Backend of Frontend) is a business flow or just data flow. Let's discuss these details in the characteristics section in this chapter.
Web applications are usually 6 basic components. Brokers, Services, View Services, Bases, Components and Pages. Since we've already discussed the data flow components in the Services portion of The Standard. In this section, we will be discussing the UI aspect (Bases, Components and Pages) with a slight detail about view services.
Let's discuss these charactristic here.
UI components consist of base, components and pages. They all play the role of separating the responsibility of integration, rendering and routing users to a particular UI functionality.
Let's talk about these types in detail.
Base components are just like brokers, they are wrappers around native or external UI components. Their main responsbility is to abstract away any hard dependency on non-local UI capability. For instance, let's say we want to offer the capability to create text boxes for data insertion/capture. The native <input>
tag could offer this capability. But exposing or leveraging this tag in our core UI components is dangerous. Because it creates a hard dependency on non-abstract UI components. If we decide at any point in time to use some 3rd party UI component, we would need to change these native <input>
tags across all the components that use them. That's not an optimum strategy.
Let's take a look at a visualization for base component functionality:
As seen the above example, base components will wrap an external or native UI component then expose APIs to allow the interaction with that component seamlessly and programmatically. There are occasions where these APIs will represent parameters, functions or delegates to interact with the component based on the business flow.
3.2.1.2.0.0.0 Implementation
Let's take a look at a simple Base component for solving this problem:
In the code above, we wrapped the <input>
tag with our own base component TextBoxBase
and we offered an input parameter Value
to be passed into that component so it can pass it down to the native UI element. Additionally, we also provided a public function SetValue
to allow for programmatically mimicking the users behavior to test drive the consuming component of this base element.
3.2.1.2.0.0.1 Utilization
Now, when we try to leverage this base component at the core components level we can simply call it as follows:
The @ref
aspect will allow the backend code to interact with the base component programmatically behind the scenes to call any existing functionality.
3.2.1.2.0.0.2 Restrictions
Base components can only be used by Core components or just components for short. They may not be used by pages and they may not be used by other Base components. But more importantly, it's preferred that base components would only wrap around one and only one non-local component.
And just like Brokers, Base Components do not have any logic in them. They don't handle exceptions, do any calculations or any form of sequential, iterative or selective business logic operations. These operations are either data-based where they belong to view services and downstream APIs or UI-based where they belong to Core Components.
Base components also don't handle exceptions, they don't throw their own exceptions and they don't perform any type of validations.
Core components are just like services in the data flow. They are test-driven but they are also restricted with one and only one dependency at all times. Core components leverage Base components to perform a business-specific flow. They are less generic than Base components because they orchestrate and communicate with the data flow.
Here's a visualization of core components architecture:
Core components in a way are orchestrators of UI and Data components. They will leverage one or many Base components to construct a business specific flow such as a student registration form then send the signal to view services to persist that data and return responses or report errors.
3.2.1.2.0.1.0 Implementation & Tests
Let's take a look at the implementation of a core component.
The above code shows thee different types of properties within any given component, The dependency view service which maps raw API models/data into consumable UI models. And the State
which determines whether a component should be Loading
, Content
or Error
. The data view model to bind incoming input to one unified model StudentView
. And the last three are Base level components which are used to construct the form of registration.
Let's take a look at the markup side of the core component:
We linked the references of the student registeration component properties to UI components to ensure the rendering of these components have actually occurred and the submission of data has been executed.
Let's take a look at a couple of tests to verify these states. a component has already loaded state. And post submission states.
The test above will verify that all the components are assigned a reference property and no external dependency calls have been made. it validates that the code in the OnIntialized
function on the component level is validated and performing as expected.
Now, let's take a look at the submittal code validations:
The test above validates that on submittal, the student model is populated with the data set programmatically through base component instance, but also verifies all these components are actually rendered on the screen before end-users by validating each base component has an assigned instance on runtime or render-time.
3.2.1.2.0.1.1 Restrictions
Core components have similar restrictions to Base components in a way they cannot call each other at that level. There's a level of Orchestration Core Components that can combine multiple components to exchange messages but they don't render anything on their own the same way Orchestration services delegate all the work to their dependencies.
But Core components also are not allowed to call more then one and only one view service. And in that, they stay true to the view model at all times. One views service corresponds to one core component which renders one and only one view model.
View services may do their own orchestration-level work as well, in an extremely complex flow but it's highly recommend to keep things at a flat level. These very same view services perform nothing but mapping and adding audit fields in addition to basic structural validations.
In every web application, pages are a very fundamental mandatory container component that needs to exist so end-users could navigate to them. Pages mainly hold a route, communicate a paramter from that route and combine core-level components to represents a business value.
A good example for pages are dashboards. Dashboard pages are a container of multiple components like tiles, notifications, headers and side bars with references to other pages. Pages don't hold any business logic in and of themselves, but they delegate all route-related operations to their child components.
Let's take a look at a simple page implementation:
Pages are much simpler than core or base components. They don't require unit testing, and they don't necessarily need a backend code. They just purely reference their components without reference (unless needed) and they help serve that content when a route is navigated to.
For all UI components, it's a violation to include code from multiple technologies/languages in the same page. For instance, a CSS style code, C# code and HTML markup cannot all exist at the same file. They need to separated in their own files.
The unobtrusiveness rule helps prevent cognitive pollution for engineers building UI components, but also makes the system much easier to maintain. That's why every component can nest it's files beneath it if the IDE/Environment used for development allows for partial implementations as follows:
StudentRegisterationComponent.razor
StudentRegisterationComponent.razor.cs
StudentRegisterationComponent.razor.css
The node file here .razor
file has all the markup needed to kick off the initialization of the component. While both nested files are supporting files for simple UI logic code and styling. With this level of organization (especially in Blazor) doesn't require any referencing for these nested/support files. This may not be the case for other technologies so I urge engineers to do their very best to fit that model/Standard.
All UI components are listed under a Views folder in the solution. let's take a look:
Views
Bases
Components
Pages
This tri-nature conforming organization should make it easier to shift reusable components and make it also easier to find these components based on their categories. I will leave it up to the preference of the engieneers to determine whether to break down these components further by folders/namespaces or leave them all at the same level given the nesting is in place.