Uploaded by pofas93918

DDD with architectural patterns

advertisement
Masaryk University
Faculty of Informatics
Domain-driven design with
architectural patterns
Master’s Thesis
Marek Turis
Brno, Spring 2019
Replace this page with a copy of the official signed thesis assignment and a
copy of the Statement of an Author.
Declaration
Hereby I declare that this paper is my original authorial work, which
I have worked out on my own. All sources, references, and literature
used or excerpted during elaboration of this work are properly cited
and listed in complete reference to the due source.
Marek Turis
Advisor: RNDr. Jaroslav Bayer
i
Acknowledgements
I would like to thank my supervisor, RNDr. Jaroslav Bayer, for his
guidance and valuable advice while writing this thesis. I would also
like to thank my family and friends for the patience and the support
when I did not have as much time for them as I wished.
ii
Abstract
This thesis focuses on Domain-driven design, an approach to software
development, and architectural patterns and styles. With an analysis of
the patterns in the context of Domain-driven design, the compatibility
and possibility of the combination of a given pattern and Domaindriven design are examined. An exemplary system is presented that is
built on the principles of Domain-driven design and uses the described
architectural patterns and styles.
iii
Keywords
Domain-driven design, software architecture, architectural patterns,
architectural styles, domain, bounded context
iv
Contents
1
Introduction
1
2
Principles of Domain-driven design
2.1 Domains . . . . . . . . . . . . . .
2.2 Knowledge crunching . . . . . . .
2.3 Domain model . . . . . . . . . . .
2.4 Bounded context . . . . . . . . .
2.5 Context maps . . . . . . . . . . .
.
.
.
.
.
3
3
4
5
6
7
.
.
.
.
.
.
.
.
10
10
10
11
11
11
12
12
12
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
14
14
15
16
17
17
17
19
21
22
25
28
31
35
35
41
43
3
4
Tactical patterns
3.1 Entities . . . . .
3.2 Value objects . .
3.3 Domain services .
3.4 Aggregates . . .
3.5 Domain events .
3.6 Modules . . . . .
3.7 Factories . . . . .
3.8 Repositories . . .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
Architecture
4.1 Patterns of domain logic . . . . . . . . . . . . . . . . .
4.1.1 Transaction script . . . . . . . . . . . . . . . .
4.1.2 Table module . . . . . . . . . . . . . . . . . .
4.1.3 Anemic domain model . . . . . . . . . . . . .
4.2 Architecture of bounded contexts . . . . . . . . . . . . .
4.2.1 Big ball of mud . . . . . . . . . . . . . . . . .
4.2.2 Layered Architecture . . . . . . . . . . . . . .
4.2.3 Hexagonal architecture . . . . . . . . . . . .
4.2.4 IDesign . . . . . . . . . . . . . . . . . . . . . .
4.2.5 Command query responsibility segregation
4.2.6 Event sourcing . . . . . . . . . . . . . . . . .
4.2.7 Pipes and filters . . . . . . . . . . . . . . . . .
4.3 Top-level architecture . . . . . . . . . . . . . . . . . . .
4.3.1 Monolithic architecture . . . . . . . . . . . .
4.3.2 Service-oriented architecture (SOA) . . . . .
4.4 Integration of bounded contexts . . . . . . . . . . . . .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
v
4.4.1
4.4.2
4.4.3
4.4.4
5
6
File transfer . . . . . . . . . .
Shared database . . . . . . . .
Remote procedure invocation
Messaging . . . . . . . . . . .
.
.
.
.
Designing Domain-driven design system
5.1 Requirements . . . . . . . . . . . . . . .
5.2 Analysis . . . . . . . . . . . . . . . . . .
5.3 Top-level architecture . . . . . . . . . . .
5.4 Architecture of bounded contexts . . . . .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
45
45
46
46
.
.
.
.
48
48
49
51
53
Conclusion
57
6.1 Future work . . . . . . . . . . . . . . . . . . . . . . . . . . 57
Bibliography
58
vi
List of Figures
4.1
4.2
4.3
4.4
4.5
4.6
4.7
4.8
4.9
4.10
4.11
4.12
4.13
4.14
Big ball of mud with Domain-driven design 18
Layered architecture 20
Layers used on a bounded context level 21
Hexagonal architecture of one bounded context 23
IDesign components overview 24
IDesign method used with DDD on a bounded context
level 26
CQRS applied to the whole system 27
CQRS using DDD 29
Event sourcing used together with CQRS and DDD 32
Pipes and filters inside a bounded context 34
Monolithic architecture 36
Layers used on a system level 38
IDesign – bounded context mismatch 40
Bounded contexts with different architectures 44
5.1
5.2
Bounded contexts and subdomains of UniSys2. 52
Overview of top-level services. 53
vii
1 Introduction
As information technologies were gaining popularity in the second
half of the 20th century, requirements on information systems grew
rapidly. Small applications evolved into complicated systems with
rich features, adapting to the increasing demands of customers. As
the size of a system grows, so does its complexity, which can make the
system harder to understand, maintain, and evolve. New projects did
not have it any easier. Large projects were a particular challenge for
architects who needed to design systems that would accommodate
the requirements of customers, prove useful in practice, and solve
the problems the software was intended to tackle. Failing to correctly
grasp the requirements of a customer resulted in a system that was not
useful and did not deliver the solution to the problems that mattered
most.
In 2003, Eric Evans published his book, Domain-Driven Design:
Tackling Complexity in the Heart of Software [1] that tries to tackle the
challenges of designing large systems. In the book he introduced
Domain-driven design (DDD), an approach to software design, with
its focus on a modeled domain and collaboration between domain
experts and developers throughout the entire software development
process. The main contribution of Evans’ work lays in introducing
practices, principles and patterns that help in all steps of designing
software, from requirements engineering to designing implementation
solutions.
Since the publication of Evans’ book, DDD was examined and
described in many derivative books and articles. These publications
focused either on the business side of DDD or its usage at the lower
level of the system. Particularly popular are the design (tactical) patterns that Evans presented. However, a broader work that would focus
on DDD in high-level design and its usage in combination with different architectural patterns is missing. This thesis focuses on presenting
various architecture patterns and examines their compatibility and
usage with DDD.
The structure of the thesis is as follows. The first part introduces
DDD with its principles and patterns. Next chapters focus on architecture patterns and styles. Given patterns are always briefly described
1
1. Introduction
and, as the main goal of this thesis, their usage with DDD is analyzed,
mainly their compatibility with the goals of DDD and the possibility of using them in DDD systems. The most important patterns are
demonstrated in practice on an exemplary system.
2
2 Principles of Domain-driven design
Domain-driven design is a development philosophy that was defined
by Eric Evans in his book Domain-Driven Design: Tackling Complexity in
the Heart of Software. It is an approach that emphasizes the importance
of a domain, its proper understanding and collaboration of domain
experts with developers. It also presents patterns that can be used
together with gained knowledge to design the best possible solution.
The main areas where DDD can help are:
∙ knowledge crunching,
∙ collaboration of domain experts with developers,
∙ system design,
∙ refactoring.
2.1 Domains
A Domain is basically what an organization does and the environment
it does it in [2]. It is an area in which the company operates, and the
software is intended to solve problems in this area. A domain can be
decomposed into smaller parts, subdomains. Domain-driven design
differentiates three types of subdomains:
∙ core domain,
∙ supporting domain,
∙ generic domain.
Core domain
A core domain is the most important part; it is the heart of the business.
It is what makes money and what is the business most focused on and
it has the primary importance to the success of the organization [2].
For example for e-shops, the core domain is selling of goods. The
core domain is a subdomain where DDD should be applied the most.
3
2. Principles of Domain-driven design
It is essential that it gets the most attention and that it is modeled,
designed and developed as good as possible. This should be achieved
by the increased involvement of domain experts and participation of
the best developers in the team [3].
Supporting domain
Supporting domains are domains that are not the main focus of the
organization, but they help to support core domains. For example
for e-shops, warehouse management is not a core domain, it is not
what makes money, it only helps to support the core domain of selling
goods.
Generic domain
A generic domain is the least important domain for an organization. It
is a subdomain that many systems have, such as sending newsletters.
Generic domain doesn’t require much attention and in an ideal case an
existing product can be used to save time which can be rather invested
in the core domain.
2.2 Knowledge crunching
Domain-driven design puts the domain at the center of every stage
of software development. To design a useful system, developers need
to understand the domain, the processes that happen in given situations, what is important and how it is achieved. People who provide
knowledge about the domain are called domain experts.
Domain experts and developers should frequently interact and
during the whole development process. Even though this interaction
can be time-consuming for both, domain experts and developers, it
will pay off in the long run. The ideal situation is when domain expert
can be a permanent part of a developer team. The important part is
direct and ongoing interaction between domain experts and developers, knowledge crunching based on traditional waterfall software
4
2. Principles of Domain-driven design
development model1 , in which domain analyst will in requirements
engineering stage get knowledge from experts and then pass it to
developers, will most likely not lead to best results. This is because
developers are limited to the knowledge which domain analyst gained
and which he found useful. Furthermore, if they need further information or explanation, they either cannot get it from an expert, because
"that was done in the first stage", or they can only do it very infrequently to "not bother domain experts too much".
Ubiquitous language
To make the sharing of knowledge easier, developers and domain
experts should share a language that Eric Evans defines as ubiquitous
language [1]. It is a common, rigorous language that should be used
by everybody; it is an output of knowledge crunching and an artifact
of the shared understanding. Domain experts should be the ones
who provide correct terms for different situations. All terms should
have exact meaning, and there should be no ambiguity. That is one
of the reasons why overused words such as manager, controller or
service are usually not good names. Ubiquitous language should be
used in every aspect of software development, throughout the whole
development process. It should be used in the code with the same
terms and concepts used as class names, properties, method names,
etc [3].
2.3 Domain model
Gained knowledge about the domain is depicted in a domain model.
Domain model represents a view of the domain, designed to meet
the need of business use cases [3]. It is described using the ubiquitous language and works as a connection between domain experts
and developers which are tied together through the used language.
A domain model is not a diagram (although it can be depicted as
one), it is the idea that the diagram is supposed to convey [1]. It is not
1. The waterfall model is a version of the systems development life cycle model. It
has distinct goals for each development phase and phases are linear and sequential [4]
5
2. Principles of Domain-driven design
important that domain model is perfect, it doesn’t need to reflect the
reality completely. Its purpose is to be close to the reality, but only
from the business point of view, and it should depict what is relevant
for the business.
To make a domain model most useful, it is necessary to keep it synchronized with the actual code. Domain model that is not reflected in
the code can become irrelevant or even misleading. Therefore, modeldriven design approach was created, which advocates closely tied
domain model and code. When there is a major structural change in
the code, for example as a result of continuous knowledge crunching,
domain model should be updated to reflect the changes [1].
2.4 Bounded context
Bounded context (BC) is a linguistic boundary around a domain
model [2]. Inside the bounded context, concepts of model, like properties and operations, have special meaning and ubiquitous language is
used to describe the model. One term in one bounded context should
have one precise meaning. However, the same term can be used in a
different bounded context to describe something different.
Bounded contexts are very useful in large domains with a rich
vocabulary. In these domains, it can be very difficult to establish that all
terms have a global, precise, single and distinct meaning. For example,
the word pound can mean either unit of weight or monetary unit of
UK. If we split the shopping domain into multiple bounded contexts,
it is clear that in warehouse bounded context, the word pound will
be used to describe the weight of some goods on the stock, while in
invoicing context, pound will be used as a currency.
Ideally, a single team should be responsible for one bounded context. This way the integrity of a bounded context will be better protected – because a smaller number of people will work on one bounded
context, it will be easier for them to agree on the same vocabulary,
share the same ubiquitous language and not leak the terms into other
bounded contexts. It is important that teams are formed around created bounded contexts and not that bounded contexts are created
based on existing team structure. The later would force the creation of
bounded contexts which do not serve their purpose, to act as a linguis6
2. Principles of Domain-driven design
tic boundary, they would be forced to be bigger or smaller depending
on the team size, which can result in an unnaturally shifted context
boundary.
2.5 Context maps
The context map is an overview of bounded contexts and their relationships [1]. It is a high-level diagram that helps to visualize borders
of bounded contexts, which contexts are connected and how [3]. It
should be simple enough to be understood by domain experts and developers and it should reflect the current reality. Using context maps,
developers will get a better picture of the whole system and it will
protect the integrity of each bounded context.
There are several patterns that describe relationships between
bounded context:
Shared kernel
Shared kernel is a part of the model that is shared in multiple separate
bounded contexts [3]. Shared kernel is useful when there are bounded
contexts that share a lot of domain concepts and logic and keeping
the contexts separated and using translation maps to translate from
one context to another would be too much effort. Because there is
shared dependency, both teams need to be aware and cautious about
this. Therefore it should be carefully considered whether to use this
pattern.
Customer-supplier development
When two bounded contexts are in an upstream-downstream relationship it means that the context on the downstream end of the relationship is dependent on the upstream end [3]. Changes to the upstream
part will probably affect also the downstream part. Customer-supplier
is a more collaborative approach to the upstream-downstream relationship where teams from both bounded contexts cooperate together
to agree on an interface which will satisfy both of them.
7
2. Principles of Domain-driven design
Conformist
Conformist is also an upstream-downstream relationship, but unlike
in customer-supplier development, downstream context cannot expect
anything from upstream context, it has to conform to what upstream
context provides. The most common example of this relationship is
a dependency on an external supplier. In this scenario, downstream
context cannot expect an external provider to change its API because
of one customer.
Anti-corruption layer
The anti-corruption layer is useful when two models are too difficult
to be integrated in an easy way and close integration could jeopardize
the integrity of a model inside a bounded context. This can happen
especially when integrating old, legacy or external contexts. To avoid
dependencies on bad code, the anti-corruption layer should be used,
which serves as a translation layer between models of both contexts
[1]. This way the model inside a "good" context will be dependent
only on an anti-corruption layer, which is also part of its context.
Separate ways
Separate ways is a pragmatic pattern which advocates to not integrate
bounded context at all if it is not necessary. Integration is expensive
and sometimes benefits are small.
Open host service
When multiple contexts create anti-corruption layers to translate complicated model of the same bounded context, it can be often too much
of unnecessary, repeated work. Instead, the complicated model can
clearly define its contract, known as open host service, and other contexts will directly use this contract.
Published language
When translating from one model of bounded context to another, it
is necessary that they share a language. The used language is called
8
2. Principles of Domain-driven design
published language and should be well-documented [2]. It is often
combined with Open Host Service.
9
3 Tactical patterns
Tactical patterns are patterns that help to manage complexity in the
model. Their role is to capture and depict objects, their behavior, meaning, function and relationships between them, in a unified way. Given
pattern says how an object with particular function and characteristics
can be implemented in the best possible way to ensure readability,
maintainability or extensibility of the whole model.
3.1 Entities
Entities are objects with attributes and functions, whose unique identity is important. That means even if some attributes of the object
change, the object still has the same identity. A good example of an
entity is a person – even if a person dyes his or her hair, it still remains
the same person.
Entities are often modeled as mutable classes with unique identity
identifier (which is immutable). When we compare equality of entities
we compare equality of their identifiers.
3.2 Value objects
Value object as the name suggests is an object that is represented by
its value. They do not have identities and if they change it doesn’t
represent the same value anymore. A basic example of a value object
is a date. If we change the day part of the date, it is not the same
date anymore. Value objects are particularly useful when a class has
more attributes and some of them are acting as a group, together they
represent one value. In this case, attributes should probably be moved
to their own class which would act as one unit.
Another important feature of a value object is replaceability. It
shouldn’t matter which instance of a class is going to be used when
all attributes are the same. Thanks to immutability, it should also be
possible to share the same instance on multiple places where we require the same value. Value objects are usually modeled as immutable
classes. If we need to change a value, it’s better just to create a new
10
3. Tactical patterns
instance. Two value objects are equal when their attributes are equal.
Value objects are easier to deal with because we do not need to assure
uniqueness of the identity. That is one of the reasons why they should
be preferred over entities if possible [2].
3.3 Domain services
Domain services are stateless objects that provide domain functionality. They are introduced when there is a more difficult business
functionality which is not a direct responsibility of any of the existing
objects (entity or value objects) and usually requires a collaboration
of more objects. Domain services should be used with caution, only
when it is necessary because overuse of domain services can result in
an anemic domain model (described in more detail in 4.1.3), where all
domain logic resides in services instead of entities or value objects [2].
3.4 Aggregates
Aggregate is a group of entities and value objects that together form
a transactional consistency boundary. Aggregate as a whole should
be consistent at any point in time. Therefore, a root of an aggregate is
created, that serves as an entry point to the aggregate, other entities
and value objects are considered internal to the aggregate and cannot
be accessed directly from outside.
It is necessary to be especially careful when designing aggregates
because incorrectly created aggregate boundaries can cause problems.
Aggregate that is too big usually doesn’t perform well because to ensure consistency, while making changes to one object of an aggregate,
other aggregates need to be blocked. If the objects do not have much in
common, this is unnecessary. Generally, when designing aggregates,
it is necessary to know invariants of a model and design aggregate
boundaries based on them and not based on logical grouping [2].
3.5 Domain events
Domain event is a newer pattern than previous ones. Eric Evans
doesn’t talk about them in his book, but it is an important domain
11
3. Tactical patterns
concept. They describe the occurrence of something that happened.
Events have more situations when they are useful – they can be used
to record changes made to an aggregate, or as a communication tool
between aggregates in the same or even different bounded contexts.
Usually, an event is produced by an aggregate and other aggregates
listen to them and act accordingly.
Domain events should be ideally modeled as immutable objects.
The proper usage of ubiquitous language and proper naming is especially important. Events should be named in passed tense based
on the action that happened. For example, if a new user was created,
good event name is UserCreated.
3.6 Modules
Modules are containers of domain objects, and they help to organize
them and to further decompose the domain model. Modules should be
designed, with the low coupling - high cohesion rule in mind. Objects
(entities, value objects, events, ...) in a module should be cohesive with
one another, they should create one logical unit. On the other hand,
there should be low coupling between different modules, objects in
one module should have as few dependencies as possible with objects
in other modules.
3.7 Factories
Factory is an old pattern that was popularized in the book Design
Patterns [5]. It is responsible for creating complex objects and aggregates. It is useful mainly when aggregate comprises of many entities
and value objects and forming a new aggregate requires more steps
during which the aggregate is not consistent. Factory encapsulates the
creation logic and produces fully consistent aggregate. Factory can be
implemented as a method on an aggregate root or as a separate class.
3.8 Repositories
Repositories are used for persistence of aggregates. They encapsulate
the logic of storing, obtaining, updating and removing of aggregates
12
3. Tactical patterns
from a specific persistence store. Abstracting away the technical implementation of a store allows creating a model without thinking of
infrastructural concerns.
From a usability point of view, there are two kinds of repositories –
collection-oriented and persistence-oriented [2]. Collection-oriented
repositories act like in-memory collections, which means they do not
have any save or update method. This leads to nicer code because to
achieve modification it is enough to load an aggregate and then change
the aggregate, without calling any other method on the repository. A
downside of this is that they are more difficult to implement because
underlying persistence mechanism needs to be able to track changes
of objects and at the end of the transaction, reflect those changes to
the store. On the contrary, persistence-oriented repositories act more
like a physical store and they expose save (or update) methods which
makes them easier to implement.
13
4 Architecture
One of the advantages of DDD is that it doesn’t require the use of any
specific architecture. As discussed in the previous chapters, the models
(including the model of the core domain) reside in bounded contexts
and there exist different kinds of relationships among bounded context. This allows applying different architectural styles or patterns
either in the scope of a single bounded context or a system as a whole.
“The goal is to use just the right choices and combinations of architecture and architecture patterns” [2, p. 113].
The main goal of this master’s thesis is to describe the most common architectural styles and patterns, their advantages and disadvantages and how they can be used together with Domain-driven
design.
The following chapters describe different architectural patterns
and their application on the level of domain logic, on the bounded
context level and on the system as a whole. The main focus is given
to the patterns that directly affect DDD concepts like domain model
or bounded context. This means that some well-known architectural
patterns are omitted. This is especially true for patterns that handle
user interaction, such as Model-view-controller pattern 1 .
4.1 Patterns of domain logic
Domain or business logic is the most important part of a system or
subsystem and it lies at the heart of a bounded context. Not depending
on which architecture is chosen for a bounded context, domain logic
should be encapsulated in its own unit. This chapter presents which
patterns can be used to design and further decompose this unit. Even
though Domain model is the dominant pattern used for domain logic
in DDD and it was already discussed in 2.3, this chapter discusses
alternatives and other patterns used for domain logic.
1. https://www.ibm.com/support/knowledgecenter/en/SSZLC2_7.0.0/com.
ibm.commerce.developer.doc/concepts/csdmvcdespat.htm
14
4. Architecture
4.1.1 Transaction script
Transaction script is a procedural style of organizing domain logic. For
each use case, transaction script is created which makes calls directly
to the database or through a thin database wrapper and executes the
use case step by step first maybe doing some validation, then loading
data from a database, performing some calculation and at the end
saving results to the database. All of those steps being part of that
single transaction script [6].
The most significant advantage of transaction script is its simplicity
– it’s a simple procedural model that is well understood by developers. It is easy and fast to develop it, and it’s easy to set transaction
boundaries. However, with bigger systems, it is hard to keep it in
well-designed state. Even though one transaction script doesn’t have
to be placed only in one procedure (or method) and usage of shared
procedures is allowed, it is still prone to duplication among different
transaction scripts. In the end, for a complex domain, the system can
become a tangled web of routines without structure [6].
Transaction script with DDD
The main problem of transaction script is in its nature – it doesn’t use
object model to represent the domain, so the relationships, constraints
and rules are represented only as steps in a method, they are not
captured in a model using tactical patterns. That’s why it is very hard
to achieve the goals of DDD with transaction script for most domains.
Eric Evans says: “If the architecture isolates the domain-related
code in a way that allows a cohesive domain design loosely coupled
to the rest of the system, then that architecture can probably support
domain-driven design.” [1, p. 79].
I would say that transaction script can be used with DDD, but only
for a very small subset of domains that naturally fit the procedural style
of transaction script. This is mainly when behavior is more important
than data itself. For example, a system whose core idea is the analysis
of huge (maybe irregular) data can be a good candidate for using
transaction script instead of domain model. In such a system the
amount, irregularity or randomness of data can make it inefficient
or impossible to create object model. Main terms of the ubiquitous
15
4. Architecture
language are the actions or calculations that are performed with data
and they can be nicely captured in names of procedures (or methods).
These are quite rare situations and while transaction script can be
useful pattern, when used for core domain, usually it suggests that
system is not complex enough for DDD to be used. However, it can be
still useful for a generic domain where building a full domain model
can be a waste of resources that could be rather used for a core domain.
4.1.2 Table module
Table module is another pattern used in domain logic. Its definition lies
somewhere between domain model and transaction script. The main
difference between domain model and table module is in the meaning
of instances. In domain model one instance of a class usually represents
one instance of an entity in real life. For example, the instance of a
class User represents one user in real life. On the other hand, in Table
module, there is only one instance which is used for managing all
instances in real life [6]. Clients of this instance need to provide data
(e.g., as a list of table rows) on which the instance will work.
This pattern provides more structure than transaction script, but
it still misses the expressiveness of domain model. The most significant advantage of table module is how well it fits with many simple
tools that are used for database access, such as ADO.NET or JDBC in
Java, so it doesn’t require Object-relational mapping (ORM) tools and
complicated mapping [6].
Table module with DDD
Even though there exists some kind of organization of methods into
objects, the full object model is still missing, data are passed around
in a raw form (for example as they were returned by database wrapper). Therefore tactical patterns still cannot be used, but it is easier to
incorporate ubiquitous language into the code.
Similarly to transaction script, table module should not be used
for a core domain. If a core domain is very simple and table module is
a fitting option, in most cases it means that the domain is not complex
enough for DDD. However, table module can still be a good option
for generic and supporting domains.
16
4. Architecture
4.1.3 Anemic domain model
Anemic domain model is a situation when there is an object model
created, which should serve as a domain model, but objects that represent entities or value objects have no behavior, they are designed to
be only data holders and usually contain only getters and setters [7].
It is considered to be an anti-pattern because it contradicts the ideas
of object-oriented programming, which combines data and behavior
together. The other problem is that it is costly to create such a model –
objects and relationships still need to be crafted, it is necessary to use
ORM tools, however, it loses the benefits of rich domain model.
Anemic domain model with DDD
While the objects capture the structural view of the domain well, the
behavior with business rules, constraints and actions is placed in a
different place, usually in some service class. Because of this situation,
even though the structural view is close to reality, the behavioral view
is very different from how domain experts think about the domain.
Anemic domain model, therefore, resembles transaction script,
however with all the costs of creating object model. Therefore it should
not be used. In very specific or generic domains, transaction script or
table module can be considered instead.
4.2 Architecture of bounded contexts
The system that is divided into multiple bounded contexts provides a
good opportunity to choose an architecture for one bounded context,
independently on other bounded contexts. To be able to have architectures of all bounded context independent, also the right architecture
on the top level needs to be picked. This is discussed in more detail
in section 4.3. This section describes architecture patterns that can be
used to decompose a single bounded context.
4.2.1 Big ball of mud
Big ball of mud (BBoM) is an anti-pattern which describes unstructured, randomly interconnected code. BBoM is usually a result of
17
4. Architecture
non-existing architecture, rapid development without thinking about
design, ad hoc modifications, or just lack of knowledge about good
software development practices and principles [8].
Because the code doesn’t have any clear structure, it is very difficult to deal with it, and it becomes very hard to understand, maintain
and extend. It is ugly, bug-prone code which all developers hate to
deal with. This is especially a big problem if the BBoM is used in the
core domain of a business. Even if the system can currently serve all
demands of business, in the future it will be difficult or even impossible to adapt to changes or additions in business requirements and
progressively deliver business value.
Big Ball of Mud with DDD
Even though BBoM is generally considered an anti-pattern and should
not be used for core domain, there are situations where it can be
acceptable to use it. It can be used for low complexity or not very
important code, especially in a generic domain. It will decrease the
time to market and save resources that can be rather used for crafting
better a model in the core domain. When using BBoM, it is important
that it resides in its own bounded context. Other bounded contexts
that need to communicate with it should use anti-corruption layer
which will serve as an adapter between "bad" and "good" code. "Good"
code, therefore, doesn’t need to be aware of all the complexities of
BBoM and it will not become dependent on it.
BBoM Context
Generic Domain
"Good" context
Anticorruption layer
Core domain
Figure 4.1: Big ball of mud with Domain-driven design
18
4. Architecture
4.2.2 Layered Architecture
Layered architecture is one of the most commonly used architectural
patterns [9]. When using it, the software is logically or physically divided into layers, where each layer is at a particular level of abstraction
[10]. Each layer encapsulates it’s own logic and exposes a public interface to the layer above. Each layer can use only the layer that is one
level lower (sometimes it can use any layer that is below), dependency
on layers above is not allowed. Typically used layers are presentation
layer (used for displaying content to the user), domain layer (contains
domain logic) and data source layer (used for accessing data in the
store) [6].
Structuring software into layers makes it easier to understand –
it is possible to have a look only on one layer without the noise of
other layers. For example, it is possible to understand how particular
business operation is implemented without having to know how it is
displayed to the user and how the data are stored. It is also possible to
substitute the whole layer with different implementation if it follows
the same contract. For example, we can replace the relational database
implementation of the data source layer with NoSQL implementation.
The problem with layered architecture is that it doesn’t abide by
the dependency inversion principle 2 . The higher layers usually have
a higher level of abstraction, but they depend on lower level layers.
It is especially true for the domain layer which depends on the data
source layer which is a low level, infrastructural code.
Layered architecture with DDD
Eric Evans chose in his book layered architecture as the main architectural pattern on which he focused the most. He proposed four layers
for DDD (see figure 4.2): user interface layer, application layer, domain
layer and infrastructure layer.
2. Dependency inversion principle is a rule that states that “High-level modules
should not depend on low-level modules. Both should depend on abstraction”
and “Abstraction should not depend upon details. Details should depend upon
abstractions” [11].
19
4. Architecture
The user interface layer is responsible for showing the data to the
user and interpreting his commands. User doesn’t have to be a real
person, it can be another system.
The application layer is a thin layer with application service which
coordinates the operations on the domain layer. It doesn’t contain any
business logic it only works as a mediator between the user interface
layer and the domain layer. It orchestrates the domain layer based on
use cases required by the user interface layer and provides them as
coarse-grained API. It also takes care of cross-cutting concerns such
as transaction management or security.
The domain layer is the heart of the system, it is where all business
logic resides. It contains information about business knowledge, rules,
concepts and work flows expressed through entities, value objects,
aggregates, domain events and services.
The infrastructure layer contains technical implementations. This
can be for example access to the persistence store through repositories but also communication with other systems or other low-level
implementations.
User interface layer
Application layer
Domain layer
Infrastructure layer
Figure 4.2: Layered architecture
20
4. Architecture
Layers can be created either on a system level (described in 4.3.1)
or on bounded context level. When layers are created on bounded
context level, each bounded context is a separate unit and doesn’t
share any layer with other bounded contexts, it has its own internal
layers. This design separates the bounded contexts more, they become
more independent and can be changed or deployed independently
on other contexts, which makes it better scalable in the future. Some
bounded contexts do not even have to have the layered architecture.
The downside is that it is necessary to somehow integrate bounded
contexts. This is described more detailed in 4.3.
Bounded context
User interface layer
Application layer
Domain layer
Infrastructure layer
Bounded context
User interface layer
Bounded context
Application layer
User interface layer
Domain layer
Application layer
Infrastructure layer
Domain layer
Infrastructure layer
Figure 4.3: Layers used on a bounded context level
4.2.3 Hexagonal architecture
Similarly to layered architecture hexagonal architecture, called also
ports and adapters architecture, structures the system into separate
layers, but the most crucial difference is that it puts the domain layer
in the center [2]. Therefore this architecture abides the dependency inversion principle because domain layer, the highest level code, doesn’t
21
4. Architecture
depend on any low-level implementations. Hexagonal architecture
proposes a layer of adapters which serve as a translation between the
model of the given hexagon and the world external to the hexagon. The
outer systems communicate through the ports (for example HTTPS
protocol) with the adapters which transform the request and forward
it to the model.
Onion architecture, which was introduced by Jeffrey Palermo [12],
is very similar to hexagonal architecture. It puts the domain layer in
the core and layers can depend on layers that are more central, but
they cannot depend on layers further out from the core. Because of
its similarities, in this master’s thesis, I will not distinguish between
them, and I will consider them the same architectural style.
Hexagonal architecture with DDD
When using hexagonal architecture, it is possible to use it on a system
level as in layered architecture, but the pattern is losing it’s main
benefits (discussed more in 4.3.1). Usually, the better option is to create
one hexagon per bounded context (see figure 4.4). This option will be
better scalable in the future for the same reasons as bounded context
level layers in layered architecture. Furthermore, the pattern will show
its biggest power. The ports will serve as a communication channel
between bounded context and adapters will serve as an anti-corruption
layer. This will protect the integrity of models in the bounded context,
and the logic from one bounded context will not leak into the other
context.
4.2.4 IDesign
IDesign method is an analysis and design technique created by Juval
Löwy which mechanizes design decisions and focuses on run-time
behavior [13].
One of the main focuses of IDesign is the decomposition of a system
into components. It rejects the functional decomposition (decomposing into components based on functions or use cases) because it doesn’t
adapt well to the changes, leads to duplicate behavior in services, an
explosion of services and couples services to current use cases, which
makes it hard to reuse created service in a different use case. Instead,
22
4. Architecture
Adapters layer
Application services
Inf
ras
e
ac
erf
int
tru
er
ctu
Us
re
Domain model
Figure 4.4: Hexagonal architecture of one bounded context
it promotes volatility based decomposition, which encapsulated areas
of potential change into components. The system is then implemented
as an interaction between created components.
IDesign divides the system into layers:
∙ client layer – used by users and other systems for interaction
with the system,
∙ business logic layer – holds business functionality,
∙ resource access layer – used for access to resources such as
database, files or other systems,
∙ resource layer – the layer of physical resources,
∙ utility layer - the special, vertical layer which contains common
functions, such as logging, event handling or security, used by
all layers.
Unlike traditional layered architecture which ends here, it further
decomposes the business layer into components using volatility based
approach. There are conceptually two kinds of components – engines
and managers. Engines encapsulate business rules and logic of doing
23
4. Architecture
some activity, managers encapsulate sequences necessary to perform
a use case. Every engine works independently on its own, it cannot
use other engines, but it can use components from the resource access
layer. Managers coordinate engines to perform a use case. One manager can use more engines or resource access points and one engine
can be shared among multiple managers. Managers do not call other
managers directly, only asynchronously (for example through event
publishing).
Client layer
Client
Client
Utility
Business
logic layer
Manager
Manager
Engine
Engine
Resource
access layer
Utilities
Resource
access
Resources
DB
Engine
Resource
access
Utility
Utility
File
Figure 4.5: IDesign components overview
IDesign method with DDD
IDesign method is best used together with DDD when volatility-based
decomposition is done on a bounded context level (see figure 4.6). It
can be especially useful when the domain model inside the bounded
context is large and it would be useful to decompose it further into
smaller components or modules.
24
4. Architecture
Layers proposed by Juval Löwy for IDesign fit very well with layers
proposed by Eric Evans for DDD. The client layer in IDesign is the
same as the presentation layer in DDD, the resource access layer is
similar to the infrastructure layer, the layer of managers is basically
the application layer and the engines are like the domain layer.
Managers will serve as application services in DDD and decomposing domain layer into engines is basically creating modules in DDD.
IDesign method will drive decisions of where to put borders of created
application services and modules.
When using IDesign with DDD on a bounded context level, each
bounded context can protect its integrity thanks to client and resource
access layer. Model of one bounded context cannot access model of
another bounded context directly, but the calls need to go through
resource access and client layer. Here patterns of bounded context
relationships can be used. For example, open host service can be easily
defined as a client in the client layer, exposing its contract to other
bounded contexts. An anti-corruption layer can be created as a resource access point in the resource access layer.
4.2.5 Command query responsibility segregation
Command query responsibility segregation (CQRS) is in its core a
pattern that splits objects into two – one object contains command
methods that mutate state and do not return anything and one contains
query methods that return values without side effects [14].
We can apply CQRS pattern to our whole system and split it into a
command (also called write) part and a query (read) part. This will
significantly improve scalability options for the future. Thanks to the
separation we can independently optimize both parts. In a command
part, it can be efficient to use Object-relational mapping (ORM) tool
and in a query part, we can use raw SQL or even stored database
procedures to optimize queries. We can even deploy command and
query parts separately and scale the system horizontally. To get even
better performance, it is possible to split even the database into two.
This allows to use different schemas and normal forms, depending on
what is more convenient in a given part, but obviously, at the price of
having to keep both databases synchronized [15].
25
4. Architecture
Bounded context
Bounded context
Client
Client
Manager
Manager
Manager
Engine
Engine
Engine
Engine
Resource
access
Resource
access
Resource
access
Engine
Resource
access
Bounded context
Client
Client
Manager
Manager
Engine
Engine
Engine
Resource
access
Figure 4.6: IDesign method used with DDD on a bounded context
level
26
4. Architecture
Even though CQRS has its advantages, it brings complexity to the
system. Therefore it should be carefully considered if CQRS is worth
the effort. Lots of domains do not have their querying and modification
logic very independent and there is enough overlap for both parts to
stay merged together. Also, many systems will probably do not need
scalability level provided by CQRS [16].
Data
Simple get
Save
Command part
Query part
OK/NOK
Command
Query
Data
Figure 4.7: CQRS applied to the whole system with two databases
Because CQRS splits the system into two, usually large parts, the
architecture of each part should be considered as well. For example,
the command part can use hexagonal architecture, for the read part,
simple layered architecture is usually enough.
27
4. Architecture
CQRS with DDD
CQRS can bring multiple advantages to the systems that use DDD.
The biggest advantage is that it helps us to look at the domain model
(which resides in the command part) from a behavioral point of view.
Domain objects can focus more on the behavior and business rules
and less on how to hold or return data because they will not be used
for that. This will make the model closer to the ubiquitous language
and it will reflect how domain experts use the software.
Because we have a dedicated query part, repositories can be simplified, they will not need to take care of querying by different criteria,
but they can focus more on writing to the store. Also, the problems
with queries that cut across multiple aggregates will be solved.
When applying CQRS with DDD it is important to find suitable
bounded context and use it only there. The bounded context is divided
into two parts – the command part and the query part (see figure 4.8).
We can design command part as a hexagon (described in 4.2.3).
User will send commands through the user interface layer. Commands
will be handled by command handlers and they will invoke corresponding actions on the domain model which is at the center of the
hexagon. Command handlers have the same responsibilities as application services described in 4.2.2. Communication with the data store,
other systems and other infrastructural code is placed in infrastructure
layer.
Query part is much simpler, it doesn’t contain any domain objects
and can be designed simply with two layers – user interface layer for
user interaction and thin read layer for reading data from the store.
4.2.6 Event sourcing
Event sourcing is an architectural pattern which represents the current
state of the system as a sequence of events leading to the current state
[17]. This is different than the traditional approach where the current
state is represented merely as a snapshot of data, which is a structural
view of the system. An event represents a single action that already
happened in the past and is represented as a verb in past tense. Events
are stored in the append-only event store.
28
4. Architecture
Command part
Infrastructure
Query part
Read layer
User interface
Us
er
Command handlers
int
erf
ac
e
Domain
model
Figure 4.8: CQRS using DDD
29
4. Architecture
The biggest advantage of event sourcing is that it brings substantial
business value. Thanks to the event store, by replaying events, we can
get to any state in the history of the system. The system is also not tied
to one specific structural view, but different views can be created, in
the future views that currently are not useful can be created, events
contain all data necessary for it. Another advantage is easier handling
of mistakes. When we notice that incorrect action happened in the
past, we can easily rollback state to that point and then replay only
correct events. From a technical point of view, event sourcing also
brings better scalability. Thanks to the append store nature, no locks
are necessary and partitioning is easier to achieve.
Event sourcing brings also challenges when being implemented.
One of them is how data are going to be read when queried by the
user. They cannot be queried directly from the event store because it
stores only raw events and replaying events every time would take too
much time. To solve this, CQRS can be used which creates the second
model which basically represents the structural view of the system
at the current point in time and is used for reading. Read model is
updated every time a new event is added to the event store by an
event handler designated for this purpose. To improve the scalability,
eventual consistency is usually used when synchronizing event store
and read model.
Another performance bottleneck can happen if it is necessary to
replay too many events to get to the current state (and then to perform some action). This problem is mitigated by creating snapshots of
objects in a given state and saving the serialized state. After that, to
get to the current state, it is enough to replay events from the latest
snapshot.
To be able to extend the system with new requirements, it is necessary to be able to adapt to new versions of events. This is problematic
because the event store already contains old serialized events so it
is not possible to just rename a class attribute. A new class that represents new version is usually created. Because of different version
of an event, event handlers either need to handle all events directly,
or mechanism for transforming old events to new events needs to be
created.
Event sourcing can bring substantial business value, but it should
be carefully considered whether it is worth the effort. The domain
30
4. Architecture
should be complex enough and the events happening in the domain
should be distinct.
Event sourcing with DDD
Understanding the state as a chain of events rather than a snapshot,
makes many systems look more close to reality. In many situations,
domain experts think about a business as a sequence of events [15].
For example in the banking domain, an account is not represented as
a simple number representing account balance but as a collection of
deposits and withdrawals leading to the present.
With DDD, Event sourcing is used on an aggregate level (see figure 4.9), state of an aggregate is represented by events related to it
[2]. "Encapsulating" event sourcing in each aggregate fits very well
with transaction boundaries. Because aggregates represent consistency boundary, making events (related to event sourcing) internal
to an aggregate, makes sure the boundary is not crossed. Also, a nice
side effect of Event sourcing is that it makes API of an aggregate more
behavior-oriented, which again is closer to the perception of domain
by domain experts [3]. The simple nature of a persistence store makes
aggregates very persistence independent, there is no need for difficult
ORM mechanisms.
4.2.7 Pipes and filters
Pipes and filters pattern brings structure to systems that deal with a
stream of data in multiple processing steps. It decomposes the system
into smaller components called filter, where each filter is dedicated for
one processing step. Filters are organized into a pipeline, where filters
are connected through a pipe [10]. It comprises four main components:
∙ Filter – it is used for processing data and transforming input to
output. The filter needs to adhere to some contract that specifies
the format of input and output data.
∙ Data source – the source of data with the same structure which
are provided to the system. The data source can either actively
push or wait until filters request data.
31
4. Architecture
Eventually consistent
Blob DB
1NF DB
Event store
New
events
Update
Data
New events
Stored
events
Aggregate
Object created
Event handlers
Query executors
Event 1
Event 2
UI Controllers
Current state
Command handlers
UI Controllers
Query
Command
Figure 4.9: Very abstract diagram of Event sourcing used together with
CQRS and DDD
32
4. Architecture
∙ Data sink – it is the end of the pipeline, it can either actively
request data from the last filter or passively consume data provided by the filter.
∙ Pipe – represents a connection between filters, data source or
data sink.
Pipes and filters bring flexibility to the system because a filter can
be replaced without other filters in the pipeline being affected. Also if
filters are well-designed, they can be reused in different pipelines.
The biggest problem with pipes and filters is error handling. When
an unexpected situation occurs in the filter in the middle of a pipeline
it is often not clear how to continue. Mainly because the filter could
already process some of the input data and produced some result
which could already reach the data sink.
Pipes and filters with DDD
Pipes and filters is a very specific pattern, and not many DDD systems or subsystems will benefit from it. It cannot be used for many
typical domains that usually do not use stream-like data required by
this pattern. Also rather broad data model with event-driven behavior of many domains would make usage of filters inefficient or even
impossible in both development and also execution time.
Even though not very common, there can be a subdomain where
pipes and filters can fit. For example, a newspaper publisher company
can have text correction for articles as one of its supporting domain.
They created new algorithms for correcting text and pipes and filters
can be a good pattern for designing this subdomain. An article can be
viewed as a stream of sentences which are provided to the pipeline
with different filters – filter for correcting the use of commas, another
for correcting spelling errors, word order and so on.
Figure 4.10 shows a simple diagram of such a system. The data
source is actively pulling new articles from database. Other components are passing and they are waiting for data.
33
4. Architecture
Publishing
bounded
context
Article
DB
pull new sentences
from articles
save corrected sentences
Text correction
bounded context
Other BC
Other BC
Data source
Data sink
pipe
pipe
comma
correction
filter
word order
correction filter
pipe
pipe
spelling errors
correction filter
Figure 4.10: Simple diagram of a system using pipes and filters inside
a bounded context
34
4. Architecture
4.3 Top-level architecture
This chapter describes architectural patterns that can be used on the
highest level of the system. It will consider only complex systems with
multiple bounded contexts because for a simple system with only one
bounded context these ideas are not necessary. For systems with only
one bounded context, the patterns mentioned in the previous chapter
can be used.
4.3.1 Monolithic architecture
When structuring the system, the simplest solution is to put everything into one place. This approach creates a monolith. Monolithic
architecture composes everything into one piece, and the software is
designed to be self-contained [18].
The advantage of the pattern is its simplicity – it is fast to develop
the system (especially in the start phase) and easy to deploy (it is necessary to deploy only a single component). Also, because everything
is running in the same place (e.g., single virtual machine), there is no
delay in invocations inside the system caused by the network, which
makes the throughput higher than in distributed systems.
Because everything is in one place, there exist disadvantages to
this pattern. When not being cautious, it is easy to make the code interconnected and interdependent. Also because everything is one unit, it
is hard to scale horizontally. It is not possible to add another machine
to support only the part of the system that is causing a bottleneck.
And even though deployment is easy from a technical point of view,
the need to replace everything at once makes the system for some time
completely unavailable.
Even though the monolithic architecture is sometimes frowned
upon, when done correctly, it is a very useful pattern. It is especially
good for not very large systems that do not need huge scaling capabilities. Martin Fowler advocates starting with monolithic architecture
and moving on to something more complicated (distributed), only
when the system is too complex to stay as one unit [19].
35
4. Architecture
Monolithic architecture with DDD
From DDD point of view, the potential danger of monolithic architecture is the fact that all bounded contexts are put in the same place
without any physical boundary among them. This can make the language boundaries represented by bounded context very loose and
the language can easily leak from one context to another. Therefore
it is necessary to keep the bounded contexts separate with as few
dependencies as possible, ideally using an anti-corruption layer or
open host service.
Bounded
context
Bounded
context
Bounded
context
Bounded
context
Figure 4.11: Simple diagram showing monolithic architecture. Thin
lines between bounded contexts show that there is no physical separation of bounded contexts.
Because monolith doesn’t provide any structure, some other architectural pattern should be used to decompose the system. The following subsections provide a brief overview of patterns that can be used
for this task. Because these patterns were described in more detail in
36
4. Architecture
4.2, here will be only briefly described advantages and disadvantages
of those patterns used for the whole monolith.
Big ball of mud
Big ball of mud is an unstructured, randomly interconnected code.
Therefore it would not be possible to have clear borders between
bounded contexts and thus keep the linguistic boundary of each
bounded context. Additionally, the maintenance and extensibility
burden which comes with the pattern makes it a very bad option for
not only DDD systems.
Layers
When layers are created on a system level, the whole system is fitted
into created layers (see figure 4.12). This is usually easier to develop,
but it can have scalability issues in the future. Also, it is more prone
to becoming big ball of mud because even though the system is structured into layers, the layers itself will be probably huge.
When using layers on a whole system, all bounded contexts are
placed in one layer. Therefore it is necessary to use some additional
design pattern that will encapsulate bounded contexts and keep the
linguistic boundary.
Using layers on the system level can be considered for simpler
systems with fewer, not very big bounded contexts.
Hexagonal architecture
When using hexagonal architecture, it is possible to use it on a system
level as in layered architecture, but the pattern is losing its main benefits. Ports and adapters are designed for the communication and when
the whole system is designed as one big hexagon, there is not that
much communication happening. The only communication coming
in is from external users (or systems).
But still, this design is better than layered architecture because the
domain layer is not dependant on the infrastructure layer anymore
and it can be a good choice for smaller systems where the more inde37
4. Architecture
User interface layer
Application layer
Domain layer
BC
BC
BC
Infrastructure layer
Figure 4.12: Layers used on a system level
38
4. Architecture
pendent separation of bounded context is not necessary and it doesn’t
matter that they all reside in one hexagon.
IDesign
Using IDesign for decomposition on a system level doesn’t fit well
with Domain-driven design for multiple reasons.
In DDD, bounded contexts are created based on the domain and
the language used, they serve as a linguistic boundary around a domain model. On the other hand, IDesign decomposition is based
on volatility, components encapsulate areas of possible change. If
IDesign would be used for decomposition, components wouldn’t
match bounded contexts (see figure 4.13). Two bounded contexts could
easily extend to one component. This can happen for example when
we create a calculation component, but we do not consider domain
aspect. In this component, the calculation of product discount for customer and calculation of stock refill dates will fall, but both of those
functions will be probably part of different bounded context. While
it would be possible to draw a logical boundary between bounded
contexts in one component, there is no guarantee it will not be crossed
in the future.
Another problem of using IDesign decomposition on system level
is the difficulty of enforcing relationship patterns between bounded
contexts (described in 2.5). Even if we manage to create components
with each component exclusive to exactly one bounded context, if we
would want to create for example open host service for one BC, we
would need to create it on all components of given BC. This would be
repetitive work prone to inconsistencies.
CQRS
Dividing the whole system into two parts with CQRS would bring
huge complexity to the system. There are only several domains (or
subdomains) that have independent querying and modification logic.
CQRS should be restricted only to those parts and it should not be
applied to the system as a whole [16].
39
4. Architecture
Bounded context
Bounded context
Engine
Manager
Manager
Engine
Engine
Engine
Engine
Engine
Manager
Bounded context
Engine
Figure 4.13: IDesign – bounded context mismatch
40
4. Architecture
Event sourcing
Event sourcing requires a domain where events are distinct and advantages of event sourcing would bring substantial business value. Applying event sourcing to the whole system is not a good idea because
it would bring unnecessary complexity to the parts of the systems that
are not suitable for it.
Pipes and filters
Pipes and filters is a very specific pattern which is suitable only for a
small subset of domains that work with stream-like data. Using it on
the whole system might be not only extremely inefficient but probably
impossible.
4.3.2 Service-oriented architecture (SOA)
SOA is used by enterprises to enhance the agility and cost-effectiveness,
while reducing the burden of IT on the organization, by positioning
services as the primary means through which the solution logic is
represented [20]. It is hard to precisely define what SOA means exactly
because the term became very ambiguous and for different people
means different things [21].
From a technical point of view, SOA splits functions into distinct
units called services. Services are accessible over the network in order
to allow users to combine and reuse them when creating applications.
Services and their consumers communicate together by passing data
in a well-defined format [22].
Another definition uses more concrete terms describing it as an
architectural style for building systems based on the interaction of
loosely coupled, coarse-grained and autonomous services, where each
service exposes behavior through contracts composed of messages at
discoverable addresses called endpoints [23].
In recent years microservice architecture became very popular.
Martin Fowler describes microservice architecture pattern as “an approach to developing a single application as a suite of small services,
each running in its own process and communicating with lightweight
mechanisms, often an HTTP resource API” [24]. This description is
very close to the description of SOA. Because of the ambiguity and
41
4. Architecture
different understandings, it is hard to define the differences precisely.
For this section the differences are negligible, important is the shift
from monolith to services and even though from now on only the term
SOA is used, all described principles would apply to microservices
too.
Although there are no precise industry standards for exact composition of SOA, there exist principles and practices for service design
[25]:
∙ standardized contract – services expose their functions through
contracts, to which other services adhere to;
∙ loose coupling – services should have a minimal amount of
dependencies among each other;
∙ abstraction – services hide their inner implementation and they
expose only their contract;
∙ reusability – it should be possible to reuse the same service in
different scenarios;
∙ autonomy – services control their environment and are independent of other services;
∙ statelessness – services do not manage the state, but they defer
it to consumers;
∙ discoverability – services are described with metadata so that
they can be discovered and interpreted correctly;
∙ composability – services can be composed to create more coarsegrained services.
Names of some principles already imply some advantages of SOA
such as looser coupling or good reusability. Other advantages are for
example scalability (it is possible to independently scale the service
that is causing performance problems), availability (a service can be
deployed independently, with other services still running) or flexibility
(services can be easily changed, replaced and combined).
Even though service-oriented architecture has many advantages,
the price is the increased complexity (to adhere to principles of service
42
4. Architecture
design) and the additional overhead of the communication between
services.
SOA with DDD
SOA can be used with DDD to decompose the system into services
where each service represents one bounded context. If beforementioned principles of service design are used it has numerous advantages from DDD point of view.
Each bounded context is nicely encapsulated inside a service.
Thanks to this we can use different architectures of bounded context in different services (see figure 4.14). For one bounded context
we can choose classical layered architecture, the domain in another
bounded context can be suitable for event sourcing and the simple,
not important bounded context with generic domain, can use even
big ball of mud. Thanks to a service contract, which is basically an
open host service pattern, the architecture is kept inside the service
and implementation design doesn’t leak into another service.
Another advantage of encapsulation is the clear boundary which
is created around a bounded context. This way the potential of the
bounded context is used to the fullest – it really serves its purpose of
being a linguistic boundary. Service contract and abstraction, which
act also as a published language pattern, ensure that the language
doesn’t leak outside the service into other bounded contexts.
Because SOA brings with itself additional complexity, it is a better
fit for bigger systems with many bounded contexts, resulting in many
services, to fully use the potential of service orientation.
4.4 Integration of bounded contexts
In previous sections, the architectures that can be used for a bounded
context where described as well as the architecture on the top level.
However, still is missing the link between contexts, how they will be
integrated to fulfill use cases spanning across more contexts. There
can be a lot said about integration and integration patterns, but in
this section, integration will be described only briefly because it is
more of a technical aspect than a domain aspect. There is no need
43
4. Architecture
lay
Ad
ture
ap
ruc
ter
s
ast
Application services
Infr
er
Service 1
Domain model
User interface
Service 3
User interface layer
Application layer
Infrastructure
Service 2
Domain layer
Domain
model
Read layer
Infrastructure layer
User interface
Command handlers
User interface
Figure 4.14: Bounded contexts with different architectures organized
into services in service-oriented architecture.
44
4. Architecture
for integration in monolith application, this chapter will, therefore,
consider only SOA.
There are four main integration styles [26]:
∙ file transfer,
∙ shared database,
∙ remote procedure invocation,
∙ messaging.
4.4.1 File transfer
Perhaps the simplest option of how to integrate services is by using
files which are shared among services. If one service wants to provide information to other services it writes to a file from which other
services can read. This approach is simple to start with and has the
advantage of services not being dependent on the internals of one
another, but in more complicated systems the disadvantages of this
patter will show.
Because services are not automatically notified, it is necessary to
periodically check if some new information is present in the file (or if
a new file was created). This can be either unnecessary work if new
information is delivered rarely or there can be a big delay if a service
is not checking the file often enough. Also, all the services need to
agree on the format, name and location of the file [26].
From a DDD point of view, the disadvantage of the file transfer
integration is the low expressiveness. File transfer integrates data
rather than the functionality and it is not obvious on the first sight
how is the given process performed over the services and (in most
cases) doesn’t reflect how the scenario happens in real life.
4.4.2 Shared database
Shared database pattern goes one step further and shares the data in a
standardized way in the form of a database table. This makes it easier
for services to consume the data because the work with a database
is very common and all services will probably access some database
anyway so there is no need for working with a different format. It is also
45
4. Architecture
easier to update a single database row then updating the whole shared
file, which makes the synchronization between services easier [26].
However, shared database still has disadvantages, there is still
the need to come up with a single database schema which would all
services work with, there is still the single point which can cause bottlenecks and the expressiveness of the integration was not improved.
4.4.3 Remote procedure invocation
Remote procedure invocation (RPI) is a more direct way of integrating
than the previous two options. A service directly invokes other services
to get data or to perform some function [26]. The advantage of this
solution is that each service maintains its integrity by managing the
data on its own. The implementation is simpler than messaging, the
familiar request/response communication method is easy to achieve.
There are more approaches to how RPI can be implemented, for
example as a Remote procedure call3 or using Web services 4 with
standards such as SOAP or REST.
The disadvantage of RPI is the coupling between services. One service needs to know the location of the service it wants to invoke. This
can be alleviated by using discovery methods, but it increases the complexity of the solution [29]. Also, while it is good for request/response
interaction, other interaction patterns such as request/asynchronous
response or publish/subscribe are usually not supported.
Because of mentioned reasons, RPI is more convenient for systems
with not too many services which will be integrated in a commandbased way and not event-driven.
4.4.4 Messaging
Messaging has similar ideas as file transfer, but it tries to tackle its
disadvantages. Instead of files, services asynchronously exchange
smaller messages which can be produced after every change in the
system. Like file transfer, it decouples services, the message producer
3. Remote procedure call (RPC) is an execution of a procedure in a different address
space, which is coded as if it was a local procedure call. It is achieved by hiding the
complexity of the remote interaction [27].
4. Web service is a service that exposes its business functions over HTTP [28].
46
4. Architecture
doesn’t need to know the location of the message consumer. A lot
of complexity in transferring the message is handled by messaging
systems. The asynchronicity of the messaging improves the availability
of the system – a service can work and send messages without other
services being available to consume them.
Asynchronicity also brings problems. Even though messaging
reduces temporal inconsistencies compared to file transfer it doesn’t
remove them entirely. The asynchronous paradigm can be also more
difficult to grasp and to use and can increase the complexity of the
solution.
Messaging is a better fit for complex systems with many services
which are integrated in an event-driven way. The event-driven paradigm fits the asynchronous nature of messaging very nicely.
47
5 Designing Domain-driven design system
This chapter shows the most important architectural patterns described in chapter 4 applied when designing a DDD system. The
whole chapter talks about important stages and decisions that lead to
an architectural model. In the attachment of this master’s thesis can be
found UML diagrams that try to depict the architecture of a modeled
system. The goal of the diagrams is not to rigorously depict every
aspect of the system that would satisfy all the possible functional requirements but to show the important classes, interfaces, components
or borders that show the architecture in a concise and understandable
way.
The example system is designed for a fictional university that needs
an extension for its information system. The main goal of this chapter
is to show the architectural patterns on a real-world example. The
main focus is given on the patterns, so some things are simplified, for
example, the big part of the system is not modeled at all. This is true
for sections where patterns that were already modeled in more detail
in other sections are used. Similarly, because software architecture
depicts the higher level of the systems, the low-level parts (such as
infrastructure, transactions, authorization) are often not modeled in
detail.
5.1 Requirements
The university already has its information system for management of
students, courses, marks and so on, but they would like to extend this
system to provide support for other use cases. Two most important
areas they would like to have is support for online education and
online payments. They call this new extension of the system UniSys2,
which will extend the old UniSys1. The main function the university
requested for UniSys2 are:
∙ Possibility for a teacher to share study materials of a course
either as files or as wiki-like pages.
∙ A teacher should be able to create a test, which students will fill.
48
5. Designing Domain-driven design system
∙ The system should be able to evaluate the results of the tests
automatically.
∙ The system should allow students to communicate in forums
dedicated to every course.
∙ The university wants to have its own subsystem for money transactions. Even though currently the system supports the management of their dormitories, lunch menus or courses, the user
needs to buy them with cash or debit card in person, they cannot
pay online or using their student card.
From the experience of maintaining UniSys1, the university identified the following important non-functional requirements.
∙ Extensibility – the current extension of the system will not be
the last one. It should be easy to add support for new use cases
but also to extend currently added use cases.
∙ Adaptability – Because requirements on the added use cases
can change in the future, it should not be difficult to adapt the
system to accommodate new changes.
∙ Scalability – UniSys1 has issues with performance in high traffic
areas of the system. While it was possible to scale the system
vertical up to some point, because of the architecture limitations,
it is hard to scale it horizontally.
∙ Performance – The system should have a high throughput, especially for handling payments. If a student is paying for lunch, he
should not wait long for the payment to be approved.
5.2 Analysis
UniSys1 was designed a long time ago with very little architecture in
mind. The main focus was given on quickly adding new functions,
and the quality of the software deteriorated. That is why the university decided to allocate more money and design the new additions
to the software better. Even though there is not enough money to
re-engineer the old codebase, the new code will be designed using
49
5. Designing Domain-driven design system
DDD principles and appropriate architectural patterns. This is very
appropriate because the system will have many users so the close
collaboration with domain experts (teachers, canteen staff, Ph.D. student, etc.) throughout the whole development will make the software
more useful. Also, if DDD will be applied correctly, it will improve
the possibility to extend the system and adapt it to new requirements.
Identifying domains and bounded context
After the conversation with domain experts, the architect identified six
relevant domains in which the system extension will provide solutions:
∙ knowledge sharing,
∙ testing,
∙ result evaluation,
∙ online discussion,
∙ financial account,
∙ payments.
Because this is the extension of a system, there is no core domain
handled. Core domain, which in this cases is the management of
students and courses, is the most important part for the organization,
therefore the solution was already created before. Knowledge sharing,
testing, evaluation, financial account and payments are supporting
subdomains, they are not the core of the system, but they are still very
important and provide support for many necessary features of the
system. Even though online discussion domain is important, it is a
generic domain because online discussions are commonly used in
many different systems, not just school systems.
The architect drew the border around five bounded contexts which
will serve as a linguistic boundary:
∙ finance,
∙ test evaluation,
∙ test management,
50
5. Designing Domain-driven design system
∙ study materials,
∙ forum.
Even though in the ideal world, there would be one bounded
context per subdomain, in reality, bounded contexts do not precisely
overlap with subdomains as shown in figure 5.2. For example, the test
evaluation context, which is a boundary for evaluating test results,
overlaps mainly with result evaluation domains, but it extends a little
bit also into testing subdomain because it uses some terms from this
domain as well.
5.3 Top-level architecture
For top-level architecture, the architect had to decide between two
options – monolithic or service-oriented architecture. The original
system is a monolith, which from the start made the development
faster, but it later created maintenance and scalability problems. Scalability is an important requirement for UniSys2, therefore SOA was
chosen. This way contexts with high traffic areas (such as transactions
in finance context) can be scaled independently. Also, the use of SOA
adds benefits from DDD point-of-view as described in section 4.3.2.
Even though it is not necessary that one bounded context is created
for each service, and there are situations when this is even not good,
the architect decided that this is not the case and he proposed one
service per each bounded context as can be seen in figure 5.3.
For service integration, the architect decided to choose remote
method invocation using the REST protocol. This means that all services will be web services which will be in customer-supplier relationships. The rationale for this choice was the fact that only five
new services will be added and the rest of the system still stays
the same monolith it was before. Services provide some functions
that they can perform and most of the interaction is happening in
a request/response-oriented synchronous way which fits RMI. Messaging would also require significant changes to UniSys1 because it
currently has no support for it. Even though RMI was chosen, all the
integration will happen in anti-corruption layers of bounded contexts,
51
5. Designing Domain-driven design system
Knowledge sharing subdomain
Testing subdomain
Study materials context
Test management
context
Forum context
Online discussion subdomain
Test
evaluation
context
Finance context
Result evaluation
subdomain
Transactions
subdomain
Accounts
subdomain
Figure 5.1: Bounded contexts and subdomains of UniSys2.
52
5. Designing Domain-driven design system
so when old UniSys1 will be replaced with services, messaging can
replace current integration solution.
UniSys1 monolith
Forum service
Test management
service
Study materials
service
Finance service
Test evaluation
service
Figure 5.2: Overview of top-level services.
5.4 Architecture of bounded contexts
One of the advantages of using SOA as top-level architecture is that it is
easy to use different architecture for different bounded context. This is
convenient because each bounded context (service) has different nonfunctional requirements that can be satisfied by a different pattern.
Finance service
The finance service has two main functions – querying the current
balance of accounts and making transactions, usually by lowering the
53
5. Designing Domain-driven design system
balance of a student or a user who purchased something. Because these
are two separate functions, one writes and one reads, the architect
decided to use CQRS, which will split the system into two part – one for
writing (transferring money) and one for reading (querying account
balance).
Because the architect saw that most of the actions happening in the
system nicely fit with the event-driven model, he decided to use Event
sourcing pattern for write part of the service. This will nicely represent
the transaction domain as a sequence of events for manipulation with
accounts, which is basically the same how transactions are viewed by
domain experts. Furthermore, event sourcing has the potential to provide additional business value. Even though in UniSys2 university did
not request any analytics, in the future they might consider analyzing
transactions to provide better targeting of services for students and
employees. This will be easier to do with event sourcing architecture
because it saves all the events that happened in the system, which can
be used to build different view models for different analytic queries.
Event sourcing stores events in an event store in a raw format.
Therefore it isn’t possible to query the store to get information (such as
account balance) directly. As a result of this, a separate read database
will be created which will be updated by event handlers every time a
new event is issued. This database can be then directly used by the
read side of the service.
Because there are two databases now, it is necessary to keep them
consistent. There are two options – strong consistency and eventual
consistency. Strong consistency is achieved by updating the event store
and read database under one transaction. The advantage is that the
read database is updated immediately and the result is immediately
visible (for example in GUI). The disadvantage is the increased transactional boundary. Two databases need to be updated in one transaction
and immediately, which decreases the throughput of the write side –
it is possible to perform fewer payments in a time period. Eventual
consistency makes the read database consistent only eventually, so it
is not updated in the same transaction. This increases the throughput
of payment handling, but the result is not immediately visible in the
user interface. This can be often confusing for a user.
In the end, the architect decided to use eventual consistency because the high throughput of payments is an important requirement.
54
5. Designing Domain-driven design system
The architect assessed that in this case, it is not crucial for a user to
see the immediate result. Usually, he will pay with student card so he
cannot even notice the delay, which in most cases will be less than a
second anyway.
Test evaluation service
The main focus of the test evaluation service is to evaluate answers of
a test. This can be easy an evaluation such as checking if the correct
option was chosen but also complicated evaluation such as correcting
grammar mistakes or evaluating programming code. Evaluating will
happen typically in steps. Simple questions will usually need just one
step, but, for example, performing static analysis on a programming
code to test if a student abides by coding principles will happen in
multiple steps – step for checking naming conventions, method lengths
etc.
To make the evaluation more flexible, the architect decided to use
Pipes and filters pattern. Each evaluation step will be encapsulated in
a separate filter. Filters of related evaluations will be joined by the pipe.
The whole evaluation will be performed as running of all pipelines for
a given test. The added flexibility is conveyed in the possibility to build
different pipelines for different types of tests. It makes the service also
easily extensible – to add a new evaluation step, it is enough to add a
new filter and add it to the desired pipelines.
Test management service
For this service, the architect decided to not use any specific architectural pattern. He assessed that writing and reading logic is not
separate enough for CQRS to be useful. He also considered Event
sourcing, but he did not think that the model is event-oriented. Most
of the objects do not change their state very much. He found that if
the objects were represented as sequences of events, there is very little
potential business gain from event logs. It was obvious that pipes and
filters is not a suitable pattern because the logic of the service does not
fit the paradigm of chained processing steps. IDesign could be used to
further decompose the service into components, but the service is not
complex enough to make it worth the effort. In the end, the architect
55
5. Designing Domain-driven design system
was deciding between hexagonal and layered architecture. Because
he wanted to focus on the domain logic and make it infrastructure
independent, he decided to use hexagonal architecture which allows
this by abiding by the dependency inversion principle.
Study materials service
The study materials service provides two main functions – adding of
study materials by teachers and downloading of study materials by
students. The architect noticed that the two parts are quite separate,
so he decided to use CQRS. This will make reading and writing more
independent, and it will enable to optimize both parts independently.
It can be convenient for the reading part which will be used much
more. However, because the service is fairly simple, with a simple
model, the architect did not use any other complicated pattern like
Event sourcing. Both read and write parts will have only simple layers
to separate user interface and business logic.
Forum service
For the forum service, the architect decided to use existing, bought
solution. University doesn’t need a custom, special forum, they need
just a typical subsystem for discussions with topics and responses. To
not make other parts of the system dependant on API of 3rd party
component, the architect still decided to encapsulate the component
in a service. The service will serve as an open host service defining the
clear, independent contract of the forum functionality. Furthermore,
the service will have published language which will be in ubiquitous
language, and it will be closer to domains concepts than 3rd party
language.
56
6 Conclusion
This thesis focused on Domain-driven design and architectural patterns. Following a brief introduction to DDD, we analyzed architectural patterns and styles considered at the levels of domain logic,
bounded context and the entire system.
For the domain logic, the domain model provides the best options
when applying DDD. On the bounded context level, many different patterns could be used, depending on the domain. For a typical
domain, either layered architecture (for simple bounded context) or
hexagonal architecture will typically be implemented. More specific
domains can fit CQRS, event sourcing or pipes and filters patterns.
For an entire system, two options were described – monolithic and
service-oriented architecture. Monolithic architecture was assessed to
be a better option for simpler systems with not many bounded contexts and service-oriented architecture for more complex systems with
bounded contexts where each could have a custom architecture. To
tie it all together, four integration styles were described – file transfer,
shared database, remote procedure invocation and messaging, from
which remote procedure invocation or messaging are the best options.
In the end, an exemplary system was designed on which it was
shown how domain and non-functional requirements could drive the
selection of an architectural pattern.
6.1 Future work
The thesis focused mainly on the theory of the patterns and Domaindriven design. One of the possible extensions of this thesis can be the
extensive research of the usage of the architectural patterns with DDD
in the real-life systems and the assessment which patterns worked the
best with DDD and managed to help to reach its goals.
Another extension could be the expansion of the examined patterns
with a focus on less common patterns to describe them similarly to
those presented in this thesis.
57
Bibliography
1. EVANS, Eric. Domain-Driven Design: Tackling Complexity in the Heart
of Software. Addison-Wesley, 2004.
2. VERNON, Vaughn. Implementing Domain-Driven Design. Addison Wesley, 2014.
3. MILLET, Scott; TUNE, Nick. Patterns, Principles, and Practices of DomainDriven Design. Wrox, 2015.
4. ROUSE, Margaret. waterfall model [online]. 2007 [visited on 2018-09-12].
Available also from: https://searchsoftwarequality.techtarget.
com/definition/waterfall-model.
5. GAMMA, Erich; HELM, Richard; JOHNSON, Ralph; VLISSIDES, John.
Design Patterns: Elements of Reusable Object-Oriented Software. Addison - Wesley, 1994.
6. FOWLER, Martin. Patterns of Enterprise Application Architecture. Addison - Wesley, 2002.
7. FOWLER, Martin. AnemicDomainModel [online]. 2003 [visited on 201811-24]. Available also from: https : / / www . martinfowler . com /
bliki/AnemicDomainModel.html.
8. FOOTE, Brian; YODER, Joseph. Big Ball of Mud [Fourth Conference on
Patterns Languages of Programs (PLoP ’97/EuroPLoP ’97)]. 1997.
9. RAJ, Pethuru; RAMAN, Anupama; SUBRAMANIAN, Harihara. Architectural Patterns. Packt Publishing, 2017.
10. BUSCHMANN, Frank; MEUNIER, Regine; ROHNERT, Hans; SOMMERLAD, Peter; STAL, Michael. Pattern-Oriented Software Architecture. Wiley, 1996.
11. MARTIN, Robert C. Agile Software Development, Principles, Patterns,
and Practices. Pearson, 2002.
12. PALERMO, Jeffrey. The Onion Architecture [online]. 2008 [visited on
2018-10-07]. Available also from: https://jeffreypalermo.com/
2008/07/the-onion-architecture-part-1/.
13. LÖWY, Juval. Zen of Architecture [online]. 2013 [visited on 10.10.2018].
Available also from: https://youtu.be/Jxm2rgeuC6s.
58
BIBLIOGRAPHY
14. YOUNG, Greg. CQRS, Task Based UIs, Event Sourcing agh! [online].
2010 [visited on 2018-10-19]. Available also from: http://codebetter.
com / gregyoung / 2010 / 02 / 16 / cqrs - task - based - uis - event sourcing-agh/.
15. YOUNG, Greg. CQRS Class [online]. 2012 [visited on 2018-10-19].
Available also from: https://www.youtube.com/watch?v=whCk1Q87_
ZI.
16. FOWLER, Martin. CQRS [online]. 2011 [visited on 2018-10-19]. Available also from: https://martinfowler.com/bliki/CQRS.html.
17. FOWLER, Martin. Event Sourcing [online]. 2011 [visited on 2018-1113]. Available also from: https : / / martinfowler . com / eaaDev /
EventSourcing.html.
18. ROUSE, Margaret. monolithic architecture [online]. 2016 [visited on
2019-01-15]. Available also from: https : / / whatis . techtarget .
com/definition/monolithic-architecture.
19. FOWLER, Martin. MonolithFirst [online]. 2015 [visited on 2019-0115]. Available also from: https : / / martinfowler . com / bliki /
MonolithFirst.html.
20. ERL, Thomas. SOA Design Patterns. Prentice Hall, 2013.
21. FOWLER, Martin. ServiceOrientedAmbiguity [online]. 2005 [visited on
2019-01-04]. Available also from: https : / / martinfowler . com /
bliki/ServiceOrientedAmbiguity.html.
22. BELL, Michael. Service-Oriented Modeling: Service Analysis, Design, and
Architecture. Wiley, 2008.
23. ROTEM-GAL-OZ, Arnon. SOA Patterns. Manning Publications, 2012.
24. FOWLER, Martin; LEWIS, James. Microservices [online]. 2014 [visited
on 2019-01-13]. Available also from: https://martinfowler.com/
articles/microservices.html.
25. ERL, Thomas. SOA: Principles of Service Design. Prentice Hall, 2007.
26. HOHPE, Gregor; WOOLF, Bobby. Enterprise Integration Pattern. Addison - Wesley, 2004.
59
BIBLIOGRAPHY
27. ARPACI-DUSSEAU, Remzi H.; ARPACI-DUSSEAU, Andrea C. Introduction to Distributed Systems [online]. 2014 [visited on 2019-03-10].
Available also from: http://pages.cs.wisc.edu/~remzi/OSTEP/
dist-intro.pdf.
28. DAIGNEAU, Robert. Service design patterns. Addison-Wesley, 2012.
29. RICHARDSON, Chris. Pattern: Remote Procedure Invocation (RPI) [online]. 2018 [visited on 2019-03-10]. Available also from: https://
microservices.io/patterns/communication-style/rpi.html.
60
Attachments
∙ UniSys2-architecture.pdf
– Diagrams showing the architecture of the designed system
from the chapter 5.
∙ diagrams.zip
– Diagrams from this thesis in editable form.
61
Download