Architectural Styles and ReST

Introduction

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.

Goal

I try to provide an umbrella presentation of application architecture principles. Target audience: solution architects, developers and information analysts.

Architectural Styles

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.

Theoretical Foundation

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

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:

  1. Business Objects
  2. 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.

Domain Model

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

Technical Services

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.

Example scenario:

  1. Database (Technical Component) updates Contract::contractDate (Business Service)
  2. Technical service sends a message first to Business Service Contract
  3. Contract fires change event and optionally executes additional business logic
  4. 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
  • Databases
  • Security components
  • Etc.

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.

Integration Services
Kinds of Technical Services

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 Connectivity

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.

Framework guidelines

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.linkBusinessServiceWithTechnicalComponent(businessService, technicalComponent);

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.

Connecting Persistency

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.

Technical Components

What are Technical Components?

Examples:

  • Databases
  • 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.

Service Architecture Framework

The Service Architecture Framework or SAF describes a simple implementation in Java of a publish-subscribe mechanism for domain objects. This is a simple and effective strategy to implement a plugin-like architecture for domain driven design. We have written about Business Centred Architectures (in Dutch: Business Centred Architecturen) in which you could see some examples using Smalltalk.

There is example Java code available on GitHub: https://github.com/robject/saf-service-framework

Project overview

This is the source code structure from the repository referred to above:

All classes are commented according to the javadoc styles.

The adapter package contains the relevant classes, with two packages in it:

  1. examples — this contains example classes to show how to use the framework with your own domain classes
  2. tests — the JUnit tests for the framework classes
We created a special exception class, which as you can see is placed in the exceptions package.

Framework Overview

Below you can find the overview UML class diagram of the framework.

As you can see in the picture above, the base of the framework consists of an observable – observer pair, as is customary in all existing MVC-like architectures.

All your domain classes are supposed to inherit from ChangingObservable, which is a slightly modified standard java.util.Observable class. This is a prerequisite that might impose too much on your existing classes — Java is a single inheritance language and you may not be able to subclass your existing domain classes from this class. In that case you will need to explore other strategies.

The IValue interface class contains the interface your observer classes need to conform to. This requirement will impose no restrictions on your code.

The basic idea behind the framework can be summarised as follows:

  1. Domain objects are ChangingObservables, containing the minimal code to function as such: your domain objects only send setChanged() to themselves. They do this anytime something happens that corresponds to an internal state change. The method is implemented in the superclass of course.
  2. Observers can subscribe themselves to events from the domain objects — however the code that does this is not supposed to be written by the application developers. It is part of the framework, and we will show how these connections are made.
  3. Technical components are never directly linked to from the domain objects: the events fired by the domain objects are caught by one or more Adapters, and propagated to the technical component. Examples of technical components are GUI elements (as in the original MVC), but may just as well be persistence connectors (such as Hibernate), logging components, etc.

The result is a business domain that is almost perfectly isolated, to be maintained and extended in isolation by a dedicated developers group, in close cooperation with the business users and experts themselves, or product owners if you use scrum.

Time for an example. Say we have a domain class named Person:

package reflektis.saf.adapter.examples;

import reflektis.saf.adapter.ChangingObservable;

/**
* @author Rob Vens
* @version 1.0
* @created 28-May-2005 14:40:57
*/
public class Person extends ChangingObservable {

  /**
  * The name of the person.
  * Initialize to an empty string.
  */
  private String name = "";

  /**
  * The address of the person.
  */
  public Address m_Address;

    public void finalize() throws Throwable {
    super.finalize();
  }

  public Person() {

  }

  /**
  * @return Returns the name.
  */
  public String getName() {
  return name;
  }

  /**
  * @param newName
  * The name to set.
  */
  public void setName(String newName) {
  this.name = newName;
  this.setChanged("name");
  }

  /**
  * @return m_Address.
  */
  public Address getAddress() {
  return m_Address;
  }

  /**
  * @param m_Address
  * The address to set.
  */
  public void setAddress(Address newAddress) {
  this.m_Address = newAddress;
  this.setChanged("address");
  }
}

Nothing fancy about this class, it is a vanilla implementation of a domain class. Notice that the only places you can see that this domain class is a bit different are the sendings of setChanged() to themselves, in lines 44 and 60. This method, implemented in a superclass, eventually sends notifyObservers() to a collection of objects that at some time in the past have registered themselves as such with the domain object. This line of code is the only thing developers of domain objects need to do, every time something happens in a domain object that can be interpreted as a state change. That is all. Once these hooks are in place, everything from persistence, logging, user interface linking and so forth will be taken care of. Note that domain class developers never do anything with these observers directly! They only send a message to themselves.

Remember: the goal was to make it possible for domain modelling and implementation to be done in relative isolation, with a dedicated group of developers, focussing on delivering the domain functionality. The way these domains should be created need to give less attention to possible user interfaces than is usually the case. User interfaces should be seen as technical components, like views into the domain offering more or less handles to touch (and possible change) the domain objects.

Of course, the interesting part (at least for the purpose of this article, domain modelling is certainly interesting enough in itself!) is what happens before and after.

Before: how do observers get registered with the domain objects?

After: how do change events from the domain object get propagated to technical components, so that user interfaces stay in sync, events are logged, changes to domain objects are persisted in a database?

The answer to both is that this should be taken care of by the framework, and not by code written and maintained by developers in projects. Let’s zoom in on this a bit more.


This is the update method in the class AspectAdapter:

/**
* Test for value strings here and only update observers when the argument
* indicates that we get an update of the aspect I am interested in.
* Creation date: (10-5-2001 17:26:01)
*
* @param sender
* the object that wants to notify its observers
* @param anAspect
* argument containing info on the kind of change
*/

public final void update(final Object sender, final Object anAspect) {
  if ((sender == subject && anAspect.equals(this.aspect))) {
    // make sure the changed flag is set
    // otherwise the notification is not done
    this.setChanged();
    this.notifyObservers(anAspect);
  } else {
    // effectively no-op
    super.update(sender, anAspect);
  }
}

 

The method above is the method that must be implemented by the technical services that want to subscribe to events from the domain objects. In this case this is an adapter that is created and parameterised to listen to a specific change event in the domain. For example, let’s assume we have a persistency adapter listening to the address aspect of a Person object.

Business-centred architectures I

We apologise, but this article is currently only available in Dutch. However, the article series of which this is the first is so much a compilation of the core of my thinking on architectures, that I hope to do the translations soon. In the meanwhile you might try Google Translate.

It is the first in a series of three, explaining an architecture framework that is business-centric. When first published, these three articles were for several months the top download on cibit.nl.

To read the article (in Dutch), please click here: Business Centred Architecturen I

To read the article translated with Google Translate, please try: Business-centred Architecture I