Transaction
Script, Smart UI and DDD
Enterprise
application design patterns include Transaction Script, Smart UI and Domain
Driven Design (DDD). Which is right for you?
I drafted
this article more than a decade ago. I was scared of being wrong then. And it
might be out of date now. What do you think?
Any list of books on software design is likely
to include the following two.
“Domain-Driven
Design: Tackling Complexity in the Heart of Software” Erik Evans 2002
“Enterprise Application Architecture Patterns”
Martin Fowler, 2002
Evans wrote of Transaction Script, Smart UI and
Domain Modelling design patterns. Fowler said: Rich Domain Model pattern is
better when things get complicated - meaning a core business function
with complex business rules.
Transaction Script pattern is better for simple
logic.
Agile development maxims include KISS and YAGNI. And “don’t write complex code where you can write simple code.” Microsoft recommends [DDD] be applied only to complex domains” (Wikipedia January 2017).
Transaction Scripts and Smart UI patterns can be easier to code and maintain. Where data-centric rules are invoked from several transactions or screens, you scan factor out data object-based modules. Where you need to publish events (for others to consume) and log events (for subsequent query and replay), Transaction Scripts can do that.
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.
A layered architecture
In layered
architecture, each layer requests services from the layer below. Dividing an
application into three layers dates back to the 1970s, long before OOP.
· 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 clients. Views: populate views/pages sent to clients. Models: retrieve and update data, applying business rules
· Data
store: A persistent data structure maintained using a database management
system.
Client
side requirements
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
The back-end application must respond to queries for
and commands on data. In short term memory, it must remember what the user is 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.
Messaging
systems may be used to integrate applications on the server-side. 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 sub procedures.” Fowler
Fowler wrote that the Transaction Script pattern is OK
for the 90% of transactions.
What about reuse of code? Fowler wrote of sub procedures. Designers factor out common subroutines shared by different
transactions. Typically, such a subroutine
accesses one entity or an aggregate entity in the persistent data structure.
It may contain a single procedure applied to that
entity. Or, it may be an “object-based” module
that encapsulates an 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. A
data abstraction layer may be introduced to translate between logical and
physical schemas. 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 and then 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.” Evans
Data models v domain models
An enterprise application typically has both a
long-term memory structure and several short-term memory structures. The persistent data structure is
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 DDD, the domain model is of a short-term memory. Being
an “object-oriented” model, it includes definitions of the behaviors that act
on the data. In complex domains; it likely features inheritance of behaviors
from super types in class hierarchies.
Why does
the cardinality of relationships look misleading in some of Eric Evans’
example? Why may a 1-to-N association in a data model appear as a 1-to-1
association in a domain? Why may an N-N association in a data model appear as
an 1-to-N association in a domain model?
Because the
domain” is the domain of a use case completed by a user from their viewpoint. In one use case/session, a customer enters one order with
several order lines, one for each 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 order lines for it. So the product manager sees the product-to-order
relationship as 1 to N.
Dividing
the domain model
In essence, the layered architecture is
· Client-side user interface: Use cases/sessions.
· Server-side application: Commands and queries, Domain events, Aggregate entities.
· Database server: Entities/tables.
The domain
model is divided into aggregates, each affected by events specific to the
entities in it.
Domain Event: A domain object that defines an event
(something that happens). Command and queries direct each event to the root
entity in one aggregate.
Aggregate: A collection of objects that are bound
together by a root entity, aka aggregate root. The root entity 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.
The need
for a data abstraction layer
Since an aggregate must be
populated with data from the underlying data source, the aggregates in the
domain model must be mapped to aggregates of entities in a data model or schema
In DDD, the
models are usually different, requiring a complex object-database mapping
layer.
Where a transaction is 1 domain event
When a command arrives at the application server, a command handler starts
a transaction, invokes a command operation on the root entity, and 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. It guarantees the consistency of changes made within
the aggregate. It performs the business logic of the
operation, which results in either success or failure. It can publish Events
that record the result of business logic performed and/or data updated.
Where a transaction is more than 1 domain event
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 transaction, then extra “compensating
transactions” may be needed to restore data integrity. Read my article on microservices for more.
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.