Building Microservices Through Event-Driven Architecture, Part 2: Domain Objects and Business Rules
In this tutorial, an architect explores how microservices teams can work with domain objects while creating an event-driven architecture for their app.
Join the DZone community and get the full member experience.
Join For Freethis tutorial is the second part of a series: building microservices through event driven architecture .
during this journey, i will implement the domain model: edusync.speech.domain,
this is the innermost layer that holds the core domain. it contains our domain objects and business rules. and defines our external interfaces.
databases, network connections, filesystem, ui or special frameworks are not allowed.
the core domain has no knowledge of anything outside of itself.
those dependencies and their implementations are injected into our core domain using interfaces.
at the end of the previous step, we ended up with an anemic domain model. so let's start by enriching it.
rich domain model
an anemic domain model is an antipattern in the world of ddd, so, in this section, i will decouple the domain model from data contracts using value object.
an anemic domain model is a domain model where data and operations upon that data are separated from each other. in other words, a class with only properties and methods that treat these properties are located in other classes.
as a result, these other classes can read the data but also modify it. so the domain class must have public setters. this is a lack of encapsulation antipattern:
let us begin by validation the title.
my first test will be: the title length must be greater than 10 characters and less than 60 characters
.
the test will fail, so let us implement title validation.
title value object
the main difference between the entity and value object is how to identify them.
an entity is identified by reference equality and identifier equality.
a value object is identified by reference equality and structural equality
- reference equality: two objects are equal if they reference the same object in memory.
- identifier equality: two objects are equal if they have the same identity.
- structural equality: two objects are equal if all their members are equals.
an entity has an id field and is mutable while a value object have no id field and is immutable.
a value object does not make sense without an entity, as it must belong to an entity.
consider the following situation:
- two vehicles of the same model, that have the same colors, are the same age, etc. are always two different vehicles because each vehicle has its own identifier: 'vehicle' is an entity.
- two addresses that have all their fields equals (same street number, same city, same country, etc.) are exactly the same address: 'adress' is a value object.
my first implementation of
title
looks like this:
let us fix the test:
remember that a value object is identified by reference equality and structural equality.
so right-click the
title
class and select generate equals and gethashcode.
title
has only one value, so select it and click ok
the
title
is now a value object and it's final implementation will look like this
here is the unit test of the
title
value object. i should verify that two titles are equal if they have the same values, and different if not.
url value object
all the logic to validate a url is implemented inside a value object whose name is
urlvalue
.
type value object
all the logic to validate a speech type
is implemented inside a value object whose name is
speechtype
.
and the
speech
domain object looks like this:
entities and aggregates
remember that an entity is identified by reference equality and identifier equality and has an id field. so let us create a base entity class : entity<t> , and generate equals and gethashcode on the id field. if two entities, e1 and e2, have the same id, then e1==e2 should return true
"a ddd aggregate is a cluster of domain objects that can be treated as a single unit. an example may be an order and its line-items, these will be separate objects, but it's useful to treat the order (together with its line items) as a single aggregate." -
martin fowler
an aggregate should be always in a valid state and each aggregate should have a root which is an entity, and classes that do not belong to this aggregate can only reference the aggregate root.
so, let us create a base class, aggregateroot<t> , that inherits from entity<t> . i make it generic because t is the type of the id field. and it can vary according to these entities:
domain events
domain events enable communication between bounded contexts by avoiding direct calls. so a bounded context, b1, raises an event and one or more bounded contexts, b2...bn subcribers to this event, should handle the event to consume it.
so let us create a base class called
domainevent
:
but here, because of my strategy of implementing event sourcing, all the events produced by my bounded context will be saved in my eventstore. and other bounded contexts, services or other programs interested in these events will have to subscribe to a service bus.
for example, each time i create a new speech, then i will create a
speechcreatedevent
event:
the
speechcreatedevent
class
must inherit from the
domainevent
base class:
the final implementation of my aggregateroot will look like this :
and because speech entity is the aggregate root, let us go ahead and inherit it from aggregateroot<guid>, use the id field of speech entity as a guide:
let us add a few tests to cover domain events:
logcorner.edusync.speech.application and logcorner.edusync.speech.domain yield 100 percent code coverage:
thank you for reading!
the source code is availabe here: registerspeechdomain .
Published at DZone with permission of Gora LEYE, DZone MVB. See the original article here.
Opinions expressed by DZone contributors are their own.
Comments