Transaction Script, Smart UI and Domain
Driven Design
Copyright 2014-17 Graham Berrisford. One of
about 300 papers at http://avancier.website. Last updated 23/04/2019 18:30
Any list of books on software design is likely to include these two.
· “Enterprise Application Architecture Patterns” Martin Fowler, 2002
· “Domain-Driven Design: Tackling Complexity in the Heart of Software” Erik Evans 2002
Evans wrote of Transaction Script, Smart UI and Domain Modelling design patterns.
Fowler said Transaction Script pattern is better for simple logic; Rich Domain Model pattern is better when things get more complicated.
This paper outlines these and related patterns.
Contents
Hierarchically-layered
client-server architecture
An enterprise application enables or supports one or more business roles and processes.
It encapsulates some business rules, business knowledge or business capability.
A few enterprise applications are purely algorithmic, or need very little stored data.
But the majority maintain, or at least need access to, a substantial business data store.
Dividing an application into three layers dates back to the 1970s - the days of COBOL and pre-relational databases.
Fowler says an enterprise application is typically composed of three layers, as shown below.
Client-side
user interface |
Typically HTML pages
and javascript running in a browser on the user's
machine |
Server-side
application |
Controllers:
handle requests/commands from the client Views: populate
views/pages sent to the client. Models: retrieve
and update data, applying business rules |
Data
store |
A persistent data
structure maintained using a database management system. Typically a
relational DBMS, but there are many data store varieties. |
The layers form a client-server hierarchy, meaning each layer requests services from the layer below.
The
requirements for most enterprise applications may be seen from the client side
and the server side.
Client side requirements
On the
client side, the users require a user interface, usually divided into views.
The views
are designed to what the user want to do in different use cases/sessions.
And
within each use/case session, the user enters queries and commands
the server-side application must respond to.
Server side requirements
On the
server side, the back-end application must respond to the queries and commands.
In short
term memory, it must remember what the user is currently doing in one session.
In long
term memory, it must remember more persistent data – e.g. about products,
customers, and all the orders they have placed.
Despite the OOPers mantra that business rules belong in the middle layer, some regard the data layer as the best place for data-centric business rules.
Middleware may be used to connect layers, but Fowler says "smart end points, dumb pipes", meaning don't put business rules in middleware.
The queries and commands that a server-side application are often called transactions.
Commands are update transactions which maintain the integrity of data within a given data store.
“Most business applications can be thought of as a series of transactions.
Each interaction between a client system and a server system contains a certain amount of logic.
In some cases this can be as simple as displaying information in the database.
In others it may involve many steps of validations and calculations.
A Transaction Script organizes all this logic primarily as a single procedure, making calls directly to the database or through a thin database wrapper.
Each transaction will have its own Transaction Script, although common subtasks can be broken into subprocedures.” Fowler
Fowler
wrote that the Transaction Script pattern is OK for the 90% of business
database systems in which most transactions have simple logic.
And that “Common subtasks can be broken into subprocedures”
Designers
optimise and reuse code by factoring out common subroutines shared by different
transactions.
Typically,
such a common subroutine accesses one entity or aggregate entity in the
persistent data structure.
It may
contain a single procedure applied to that entity by two or more transactions.
Or, it
may be an “object-based” module that encapsulates an entity or aggregate
entity, and so contains all procedures that access them
Erik Evans says “the Transaction Script separates UI from
application, but does not provide for an object model.”
However, there is a domain model in the form of a logical data model and/or a
physical database schema.
If the database schema differs
from the logical data model, then a data abstraction layer may be introduced to
translate between the two.
Or else,
object-based modules can be designed to encapsulate elements in the physical
database schema.
Evans proposed using what might be regarded as simpler pattern for simple UI-centric applications.
“Put all the business logic into the user interface.
Chop the application into small functions and implement them as separate user interfaces, embedding the business rules into them.
Use a relational database as a shared repository of the data.
Use the most automated UI building and visual programming tools available.
Advantages
• Productivity is high and immediate for simple applications.
• Less capable developers can work this way with little training.
• Even deficiencies in requirements-analysis can be overcome by releasing a prototype to users andthen quickly changing the product to fit their requests.
• Applications are decoupled from each other so that delivery schedules of small modules can be planned relatively accurately, and expansion of the system with additional, simple behavior is easy.
• Relational databases work well and provide integration at the data level.
• 4-GL tools work well.
• When applications are handed off, maintenance programmers will be able to quickly redo portions they can’t figure out since the effects should be localized to the UI being worked on.
Disadvantages
• Integration of applications is difficult except through the database.
• There is no reuse of behavior and no abstraction of the business problem
• Rapid prototyping and iteration reach a natural limit because the lack of abstraction limits refactoring options.
• Complexity buries you quickly, so the growth path is strictly toward additional simple applications. There is no graceful path to richer behavior.
If this pattern is applied consciously, a team can avoid taking on a great deal of overhead that is required to attempt other approaches.
The common, costly mistake is to build an infrastructure and use tools much heavier weight than are needed, or to undertake a sophisticated design approach that you aren’t committed to carrying all the way.
Most flexible languages (such as Java) are overkill for these applications and will cost you dearly; a 4-GL style tool is the way to go.
Remember, one of the consequences of this pattern is that you can’t migrate to another design approach except by replacement of entire applications, and that integration is only through the database. Therefore, a later attempt to use Java will not be helped very much by the use of Java in the initial development.
Don’t think you’re building a flexible system just because you’re using a flexible language.”
Data models v domain
models
An
enterprise application typically has both a long-term memory structure and
several short-term memory structures.
The long-term memory of an application is stored in a persistent data structure - typically defined in a logical data model.
The short term memory of each use case or session is stored temporarily in a cache on an app server.
In
domain-driven design, the domain model is a model of the data in such a
short-term memory.
Being an “object-oriented”
model, it includes definitions of the behaviors that
act on that data.
And in complex domains; it may feature inheritance of behaviors from supertypes in class hierarchies.
A N-N association in a logical data model, may appear as an 1-to-N association in a domain model.
E.g. The association between customer and product is the data
model is N-N.
In one
use case/session, a customer enters one order with several order lines, one
each for one product to be ordered.
So the
customer sees the order-to-product relationship as 1 to N.
In
another use case/session, a product manager enters a product number, and looks
through the several order lines for it.
So the
product manager sees the product-to-order relationship as 1 to N.
Similarly, a 1-to-N association in logical data model, may appear as an 1-to-1 association in a domain model.
(This explains why the cardinality of association relationships looks misleading in some of Eric Evans’ examples.
Might DDD better be called View-Driven Design.)
Dividing the domain model
Domain-driven design divides a domain model into aggregates, each of which are affected by events specific to that domain.
Aggregate: A collection of objects that are bound together by a root entity, otherwise known as an aggregate root.
The aggregate root guarantees the consistency of changes made to its members.
External/client objects can only access the root object (core entity); they cannot hold references to its member objects.
Domain Event: A domain object that defines an event (something that happens).
DDD direct each event to the root entity in one aggregate.
Client-side user
interface |
Use cases/sessions |
|||
Server-side
application |
Commands and queries |
|||
Domain events |
Domain events |
Domain events |
Domain events |
|
Aggregate |
Aggregate |
Aggregate |
Aggregate |
|
Database server |
Entities/tables |
Entities/tables |
Entities/tables |
Entities/tables |
The need for a data abstraction layer
When an aggregate is instantiated, it must be populated
with data from entities/tables in an underlying data source.
So the aggregates in the domain model must be mapped to
aggregates of entities in a logical data model or physical database schema.
Since DDD
is usually used where the models are different, it has the particular
disadvantage of requiring a complex object-database mapping layer.
Where 1 transaction = 1 domain
event
When a command arrives at the application server, a command handler:
·
starts
a transaction
·
invokes
a corresponding command operation on the root entity
·
commits
or rolls back the transaction
The command operation on the root entity:
·
plays a role akin to the control procedure in a
transaction script
·
guarantees
the consistency of changes made within the aggregate
·
performs the business logic of the operation, which
results in either success or failure.
·
can publish Events that record the result of business logic performed
and/or data updated.
Where 1 transaction = N domain events
Where a
transaction command spans two or more domain events, affecting two or more aggregates,
life becomes complex.
If each
domain event is coded as a discrete transactions, then
extra “compensating transactions” may be needed to restore data integrity.
DDD is
best used when the domain is a core business function with inherently complex
business rules.
It can
over-complicate what could be a simple system.
Many programmers would sympathise with the maxim: “don’t write complex code where you can write simple code.”
But at the same time, being bright, they get bored with what is simple and repetitive, and like to explore what is complex.
This may explain the space devoted to Domain-Driven Design (DDD) by Fowler and Evans.
A solution architect might well see minimising the amount of programming skill needed to maintain an application as a goal.
And that might mean avoiding DDD until it is demonstrably of benefit.
“Microsoft recommends that it be applied only to complex domains” said Wikipedia January 2017.
It seems likely that many (most?) enterprise applications are more easily designed and maintained using Transaction Script or Smart UI patterns.
You simply factor out data object-based modules where data-centric rules are invoked from several transactions or screens.
Transaction scripts can equally well publish Events (for others to consume) and log Events (for subsequent query and replay).
Command Query Responsibility Separation
(CQRS) and data replication
These
work together because the CQSR pattern separates Command and Query application
components.
This
helps you to separate underlying data stores (between update and reporting) if
required.
DDD and CQRS
These work together because both separate the processing
of update Commands and Queries.
Commands trigger update transactions which act on
aggregates in the Domain Model.
Queries can be done using a different and more efficient
approach, like directly executing stored procedures through a thin API Layer.
DDD, CQRS, and Event Sourcing
Where these three
are combined, the root entities in a Domain Model are responsible for:
·
accepting
input Commands (from a Command Handler)
·
validating
Commands
·
applying
Commands to data within the aggregate entity
·
saving one or more Events (via transactions) in an event
store.
But transaction scripts can equally well publish Events (for others to consume) and log Events (for subsequent query and replay).
And you don’t need DDD or CQRS to design microservices,
separate database update transactions from queries, publish Events, or use
Event Sourcing.
Microservices
You might use “microservices” to maintain discrete data stores, but the usual trade offs apply.
Read “Microservices” for more.