Realising a consistent approach in your implementation projects is quite a challenge. One of the tools I have applied successfully is to introduce some kind of reference architecture or a reference model, or even better: both, in a reference implementation. This document outlines a high-level application architecture that I have found to be very useful in building such a reference implementation.
I try to provide an umbrella presentation of application architecture principles. Target audience: solution architects, developers and information analysts.
Every system is developed according to one or more architectural styles — whether explicitly or implicitly, consciously or not, consistently or not. It is never that there is no architecture. In fact even in organisations investing heavily in modelling architecture most of the architecture will always remain “undiscovered”. A pragmatic approach has shown to be most effective: discovering and uncovering gradually, based on pragmatic and strategic considerations.
The goal is to apply these styles consciously and consistently, thereby reaping the benefits and manage the implications of using a specific style.
The architectural styles are made explicit based on the dissertation of Roy Fielding (2000): Architectural Styles and the Design of Network-based Software Architectures: https://www.ics.uci.edu/~fielding/pubs/dissertation/top.htm.
This dissertation was the foundation of the definition of RESTful services that have become the de-facto standard for service connectivity, especially for front-end services.
Over the years I have found his overview of architectural styles (not just the RESTful ones!) very useful. Especially for a business-centred architecture it is possible to very succinctly define which style should be used in what part of the architecture (primarily front-end, service layer, business component, back-end connectivity).
Business services are about the “how and what?”. Business logic is the core of any system. “Splintering” of business logic is a known and pervasive problem:
- Functional changes have unpredictable side-effects all over the place
- Identical functionality is replicated all over the place
Therefore: isolate business logic in one location. This is a logical structuring of the business. It is structured around two aspects:
- Business Objects
- Business Functions/Processes/Interactions
This isolation is quite pervasive: efforts should be made to avoid including all kinds of “technology” into this component. It should be able to “simulate” the business without needing concepts like computers, networks, databases or such.
The Domain is the component containing the business logic. This component is by itself capable of executing all desired functionality (business processes). It is a relatively isolated component, in that it is able to do that even if completely disconnected from the other parts of the architecture.
It consists of a set of business concepts that together are reflections of the “real” world of the business. If you are a car leasing company, your business domain consists of Car, Driver, Contract, etc. By including the behaviour in this model, you avoid creating this as a data model or in a set of hard-to-integrate business functions.
These domain objects are related 1-1 to Business Services in a service layer, or, as I prefer to call it, a service landscape. The Domain Model is implemented in these Business Services.
Architectural Styles for the Domain Model
Several styles come into play for this component, however the main one I prefer is Peer-to-peer.
- Architectural Styles: Peer-to-peer (Fielding). Also see Domain Model (Fowler)
- Distributed Objects (Fielding) for connecting Business Services
- (Mutual) connections through remoting
- Light-weight message queues
- Micro-services to contain the objects
- Bidirectional relations are only allowed within packages
- Package dependencies always unidirectional
- C2 (Fielding, more on this later) for firing events when something relevant happens (i.e. state changes)
- Objects know nothing about (potential) subscribers on this events
Business Services are Central (satellite model, explained in many other articles).
- Outside-in: messaging
- Inside-out: events
Any change entering through a technical service on the perimeter primarily updates a business service. This guarantees any change to be dealt with by the business logic concerned (Larman: Expert Pattern). The business object then propagates the change to subscribers, without explicitly doing that. The change is propagated “indirectly” through firing an event into the blue. The event source has no awareness about any possible event sinks.
- Database (Technical Component) updates
- Technical service sends a message first to Business Service Contract
- Contract fires change event and optionally executes additional business logic
- Synchronization services propagate for example to another database
The following sources introduced this architectural style:
Role of Technical Services
Main rule: everything is a service in an SOA. Note that the approach introduced in this article is especially effective in a micro-service landscape, although still very useful in an architecture with more coarse-grained services.
Technical services should be viewed as a synchronization mechanism between the Domain Model and the “real world”. The “real” world is kept in sync with the internal software representation (see: Through the Looking Glass).
For this synchronisation, technical components are created:
- Web (or other) front-ends
- Security components
Also known as Integration Services
Technical Services are (extremely!) simple, parameterised, standard components (framework) and thus are either used/procured (for example Spring, or an Oracle database adapter) or built only once, carefully documented and thoroughly tested to be used intensively within projects. (Extreme) light-weight, performance and bug-free are keywords when thinking about Technical Services.
Business Services ⇄ Technical Services
- Architectural style: C2 (Fielding)
- From business to technical (inside-out): notifications (events) – indirect
- From technical to business (outside-in): requests (messages) – direct
- Conceptually asynchronous (synchronous is an implementation decision, not a design decision)
- No direct link with Technical Components allowed (i.e. database, front-end): always go through Technical Services
Technical services are usually chained:
- Technical Component ⇄ TS1 ⇄ TS2 ⇄ TS3 ⇄ Business Service
Each technical service should responsible for only one integration aspect (for example data aggregation + caching), thereby creating services that are small, easy to understand and maintain, with well-defined interfaces.
Kinds of Technical Services
Below is a model of possible technical services from the Open Group, where they are referred to as Integration Services. Again, these components are used as the glue layer between business services and technology such as front-ends, legacy systems or databases. Other categorisations are possible as well. Note that these components should not be confused with Hohpe’s Integration Patterns.
Architectural Style for all Technical Services
The main style here is Uniform Interface (Fielding). Publishing of events and/or subscribing to events by Technical Services is also possible (C2 style). Technical Services are instantiated/parameterised, not programmed. Specific technical services can introduce new styles.
Front-end should be viewed as a kind of Technical Component, with which you connect (as any Technical C0mponent) through Technical Services. The architectural style here is: Layered-Client-Cache-Stateless-Server (+ Uniform Interface) (Fielding) = REST architectural style.
Spring REST Controllers for example can function as REST resource.
Business Service lookup can be done lightweight (HATEOAS), the original directory services concept has proved to be too heavy-weight.
Note that part of the architecture will evolve into a framework, and it should. Framework development should however not be a goal in itself. This has proven to be an anti-pattern in many organisations. But especially because the glue layers are so exquisitely simple, they are the ideal candidates for framework inclusion.
Integration Layer controllers all inherit from a generic Controller. This results in ca. 90% code reuse.
For example (code in generic Controller class):
This instantiates and parameterizes an integration adapter that takes care of the interface to the technical component. Adapters are only procured, or developed for the framework (not by developers in the projects). Projects only reuse them. Integration Layer Controllers can also been viewed as containers for adapters.
Usually Business Services still fetch data “directly” from databases or legacy (PL/SQL, JPA). Similar to front-end connecting this should be implemented with Technical Services taking care of persistency-as-a-service. This results in an almost complete independence from physical databases and legacy systems, enabling migration and cleanup. For this existing services need to be modified or refactored, which often feels like a daunting task.
What are Technical Components?
- Web front-end
- Security servers (for example Keycloak)
- External systems in general (Salesforce, SAP, etc.)
These are isolated or wrapped by providing them with:
- “well-defined” service interfaces
- “connectors” (sockets) to connect with
Like domain objects they are completely client-agnostic. The basic strategy is to leave them be as much as possible. If the above-mentioned styles are used you will find you are almost able to completely isolate them, which means that, at least functionally, you are able to migrate them to your leisure.