Sandwich

Sandwich

Sandwich

Follow the line of least resistance. Divide where it is loosely coupled.

Examples:

Structure: A layer of domain-level services between shared integration and data layers.

Type: Main, implementation.

BenefitsDrawbacks
Supports medium to large codebasesAggressive optimization is impossible
Multiple specialized development teamsLow fault tolerance
There are many ways to evolve the system

References: None I know of.

A Sandwich is a (sub)system midway between Layers and Services. It emerges when a layered component outgrows its architecture and its middle (domain) layer – which tends to be both the largest and least cohesive – is divided into subdomains while the application and data layers remain intact. Another, less common, origin is a (sub)system of (Micro)Services which becomes so tightly coupled that it needs to be partially merged.

A Sandwich includes the following components:

  • A shared integration layer which receives client requests and dispatches them to the underlying domain-level services. Though this layer may either implement use cases as an Orchestrator, forward each client action to a matching service as a Proxy, or do both as an API Gateway, it remains the component that ties the entire system together.
  • Domain-level services (if distributed) or modules (otherwise) that implement the bulk of the system’s business logic and thus contain most of the system’s code.
  • A shared data layer which the upper layer’s services operate on. It may be a database (provide persistence) or an in-memory application state.

Sandwiches often occur naturally without being recognized as a distinct architecture. In fact, I had to make up a name for this system topology.

Performance#

As domain-level services rarely interact among themselves, the performance of a Sandwich is similar to that of Layers with the same kind of deployment (components of a Sandwich may or may not be colocated).

Sandwich

Dependencies#

The Sandwich’s integration layer depends on every service inside the Sandwich. The services themselves depend on the data layer.

Sandwich

Having two shared layers provides three options for invoking the domain components:

  • The most common approach is orchestration by the integration layer.
  • Data-centric domains may rely on data change notifications.
  • Choreography via publish/subscribe is rare because it is inferior to the other two options:
    • It does not help to decouple the subdomain services which remain bound together by the shared layers.
    • Publish/subscribe requires additional libraries and setup, while the datastore, which often supports notifications, is already in place.
    • Furthermore, orchestration allows for much better control over use case logic and error handling than choreography.

Applicability#

Sandwich fits:

  • Medium-sized projects with complex domain logic. The subdivision of the domain layer keeps the code complexity under control and supports development by multiple teams with little increase of operational burden.
  • Rapid development of ordinary systems. If you don’t face any challenging forces, you should keep your architecture simple and stupid but still flexible – which is the pragmatic Sandwich. You will be able to evolve it in the future if needed.
  • Data-centric domains. This architecture allows all the services to work on the same dataset, each reading and writing the parts that are involved in its business logic.

Sandwich does not help:

  • Projects with a large number of use cases. As the integration layer remains monolithic, it still can grow out of control, requiring the system to transform into Layered Services.
  • Huge systems. Both the integration and data layers are cumbersome in that case.
  • Demanding forces. The Sandwich architecture is a jack of all trades, master of none. In general, it is an average backend, and is neither highly elastic, flexible, nor low latency.

Relations#

Sandwich

Sandwich:

Examples#

Though most real-world Sandwiches stay beneath the radar as non-standard architectures, there are two well-documented and highly specialized occurrences of this topology in data-centric domains and a few less stringent generic cases:

Blackboard System#

Blackboard

Some domains are complex and ill-structured: there is only a vague understanding of how the inputs relate to outputs, therefore you cannot write a single algorithm to solve it. If you are lucky to have a huge labeled dataset, you can attempt training a neural network. If there are not so many examples, you are in trouble.

Blackboard Systems [POSA1, POSA4] were invented half a century ago to tackle non-deterministic problems: speech or image recognition, X-ray crystallography of proteins, or even tracking enemy submarines or stealth aircraft. In these cases inputs are noisy and scarce and outputs can vary indefinitely, therefore one must go through trial and error proposing, refining, and comparing many possible solutions to arrive at something plausible.

The system consists of:

  • A blackboard – a shared data store that contains all the current knowledge about the problem, namely inputs and hypotheses. It is subdivided into abstraction levels which range from the original inputs through multiple intermediate representations to the output data structure. Each level usually contains multiple solution attempts.
  • Knowledge sources – independent, specialized components that process data from a lower level to create a higher-level hypothesis. For example, in voice recognition, a knowledge source may read a sound wave and write a letter which that wave encodes. Another knowledge source may read letters and output English words, while another one tries to find French words. And the final one collects words into sentences.
  • A control – a scheduler that assigns processor time to knowledge sources. It balances the quality and speed of the solution attempts based on the current progress and remaining time.

This architecture makes best use of every system layer:

  • The control is required to prune the exponential growth of possible solutions because there are not enough system resources to check all of them.
  • The independent knowledge sources are easy to add or remove, allowing the developers to try various algorithms with no changes to other components.
  • The blackboard enables the cooperation of knowledge sources, each of which is limited to a small part of the overall solution.

Space-Based Architecture#

Multifunctional - Space-Based Architecture

Space-Based Architecture [SAP, FSA] is an extremely elastic alternative to Microservices which works for data-centric domains. Each processing unit – a domain-level service – is co-located with an in-memory replica of the entire system’s data, which makes both data access and creation of new instances of processing units blazingly fast. As is common for Sandwiches, processing units can be orchestrated by the processing grid or they can subscribe to changes in the shared data grid, this architecture being flexible enough to allow for choosing a communication paradigm on per request basis.

Aside from processing units, which contain the main business logic, Space-Based Architecture involves:

  • A messaging grid which is a Proxy (combination of Gateway, Dispatcher, and Load Balancer) that receives, preprocesses, and persists client requests. Simple requests are forwarded to the least loaded processing unit (service with domain logic) while anything complicated goes to the processing grid.
  • A processing grid is an optional Orchestrator which manages multi-step workflows for complicated requests that involve branching and error handling.
  • A data grid is a distributed in-memory database. It is built of caching Mesh nodes which are co-located with instances of processing units, making database access extremely fast. However, the speed and scalability is paid for with stability – any data in memory is prone to disappearing. Therefore the data grid backs up all the changes to a slower persistent database.
  • A deployment manager is a Middleware that creates and destroys instances of processing units (which are paired to the nodes of the data grid), just like Service Mesh does for Microservices (which are paired to Sidecars). However, in contrast to Service Mesh, it does not provide a messaging infrastructure because processing units communicate by sharing data via the data grid, not by sending messages.

As Space-Based Architecture runs every component in a Mesh, it avoids the fault tolerance and database performance drawbacks inherent to Sandwich, trading them for possibility of write conflicts when multiple clients cause changes to the same piece of data simultaneously.

Service-Based Architecture#

Service-Based Architecture

Service-Based Architecture [FSA but not DEDS] is the most pragmatic and loosely defined of topologies based on Services (hence the name). In basic Service-Based Architecture subdomain services are integrated by a User Interface layer, usually a Frontend, and there is a single Shared Database. However, as there are no rules for Service-Based Architecture, multiple databases or finer-grained GUIs may be used for programmers’ convenience, disintegrating the Sandwich architecture for the sake of less coupled Layered Services.

Service-Based to Layered Services

Command Query Responsibility Segregation (CQRS)#

CQRS

Command Query Responsibility Segregation (CQRS) is a principle which calls for separation of components that process commands (requests that change the system’s data) and queries (requests that analyze the data). The subdivision necessarily happens at the domain (model) level, resulting in separate Write Model (Command Model) and Read Model (Query Model, Thin Read Layer).

In the simplest case the data layer is kept monolithic, as shown in the diagram above. That helps decouple the logic-heavy object-oriented code which maintains data consistency and business constraints during editing from the search and aggregation code that needs direct database access to run complex SQL queries. If that is not enough, it is possible to trade complexity for performance by employing specialized databases: OLTP for use with the Write Model and OLAP for the Read Model, as described in Layered Services:

CQRS to Layered Services

(inexact) Replicated Load-Balanced Services, Lambdas#

Shards - Pool

Replicating a system’s domain tier along the sharding axis also results in a Sandwich-like topology, albeit with altered properties: while the original Sandwich’s subdivision of the codebase along the subdomain axis allows for multiteam development and easy integration of new functionality, replication along the sharding axis only makes it simple to add or remove instances of the middle tier as the system load changes.

Replicated Load-Balanced Services [DDS] is the general name for running multiple instances of a stateless service that share a database and run under a Load Balancer. Lambdas is a synonym for a similar setup from cloud computing providers.

Evolutions#

The components of a Sandwich provide plenty of ways to alter the system, with unique evolutions detailed in Appendix E:

Primary evolutions#

Some evolutions involve the system’s domain logic or its topology:

  • The domain-level services are independent enough to be easily added or removed.
Sandwich add remove Service
  • In most cases they share technologies, allowing for splitting or merging of the services.
Sandwich split merge Services
  • If the services are found to be strongly coupled, they can be merged into a monolithic layer, likely to be subdivided in a better way later on.
Sandwich to Layers
  • Alternatively, the subdomains can be further decoupled.
Sandwich to Layered Services

Evolutions of the data layer#

If the data layer becomes a performance bottleneck, it can be split as a Shared Repository:

Evolutions of the application layer#

If the integration layer contains use cases and becomes cumbersome, it should be subdivided following the evolutions of Orchestrator:

  • Into Services if the use cases cluster around subdomains.
  • Into Backends for Frontends if the system serves several kinds of clients.
  • Into Layers if some use cases are simple while others are complicated.
  • Into a Hierarchy if the use cases include both generic and specialized logic.

Summary#

Sandwich is a pragmatic architecture midway between Layers and Services. It combines simplicity and flexibility, avoiding unnecessary effort for the present while retaining many paths to evolve in the future.

CC BY Denys Poltorak. Editor: Lars Noodén. Download from Leanpub or GitHub. Made with odt2wiki and Hugo Book.