Thursday, March 28, 2019

Notes about Domain-Driven Design

Ubiquitous Language

Consistent nouns, verbs, statements within the team. Your team member should know what you're saying immediately in the first sentence.




Layered Architecture



  • User interface: Obvious.
  • Application: Separate UI from Domain
  • Domain objects and business logic dwell here
  • Infrastructure: communication between layers, data persistence, supporting, shared libraries


Entity

Something with unique identity, its state matters, and should be recorded.Ex: User, people tend to ask: "What's about that user now?"
Identity comes from...
  • Attribute which is unique.
  • Combined attributes becomes unique.
  • Additional created attribute which is unique(ex: uuid).
  • Even behaviors


Value Objects

Object not Entity, not has to be unique, temporary and disposable, can be copied everywhere in the application flow

One golden rule is: if Value Objects are shareable, they should be immutable. Value Objects should be kept thin and simple.

Contradictory to its name, I think Value Object is of little value, at least not valuable as entity. To me the concept is simply that if some attributes should be grouped up, they should be grouped into a value object.

Ex: Contact is a value object, contains name, phone, email, and address.



Service

What happens in Domain Object keeps in Domain Object.
Something related to outer space, put them into Service.


3 characteristics of a Service

  • The operation performed by the Service refers to a domain concept which does not naturally belong to an Entity or Value Object.
  • The operation performed refers to other objects in the domain.
  • The operation is stateless.

Module

When objects or services or somethings become bigger, more complex, lots of exceptions to handle, nearly drive you crazy.

How to refactor this mess of codes into modules?


Find High Cohesion in module and low coupling between modules
  • Communicational cohesion: Access same data
  • Functional cohesion: Work together to do one single task.
  • Low coupling: One clear sentence to define the one single task, module interface.
    Ex: I'm a bad module, I do nothing but fuck up your app.

Aggregate

When you found some domain objects, or entities, are highly related to each other. Change to one object will need a hell lots of updates to the others, otherwise you lose data integrity. That's the time you should group them as an Aggregate object.
Ex: To buy something on the web, there have to be domain objects like Vendor, Product, Purchase, Payment, Shipment ... etc. Change to any of these objects influence data in others, so they should be grouped as an Aggregate, say Order.

There should be a Root Entity of Aggregate object, in the above example, Order.
To guarantee Data Integrity, Root Entity is, and the only one, responsible for any change or data access request from client.
For example if a client wants to change Purchase, it should tell Order to do it, because Order have to recalculate amount of Payment, cost of Shipment, and inform the Vendor.

It's very hard to create Aggregate object by merely its constructor, too much knowledge needed from domain objects and their relationships. So often we use Factory to create Aggregate


Repository

Why? To isolate data-access logics from domain-objects, otherwise they'll pollute the model, all the way up to business rules.

Repository plays as a global accessible storage, and hide the underneath physical data-access Strategy, it could be an database, or another Repository.

Repository is similar to Factory but ... The Factory should create new objects, while the Repository should find already created objects


Refactoring

Change code, but not model behavior, to make better performance, maintainability, extensibility. OR, when a new insight emerges from domain knowledge, then the model changes.
What make refactoring easier?

  • Automated test
  • Refactoring patterns
  • Refactoring tools
  • Domain experts

Refactoring Breakthrough  — When few changes make a lot of differences.

How to get to a Breakthrough?
  • Dig into domain expert's mind.
  • Review our Ubiquitous Language
  • Reconcile design contradictions
  • Find key implicit concepts
  • Make them explicit
  • Build models and relationships for them
Important concepts could come from ...?
  • Constraint: ex - a man has only 2 and exactly 2 balls. A constraint should be put explicitly into a separated method.
  • Process: ex - kick a man right in his balls. Make this kicking into Service
  • Specifications: ex - are a man's balls broke after kicked?. A specification usually test some question on model and return a boolean value.

Model Integrity

Why? Because everybody has the better idea, and no one hesitate to lay their dirty hands on your model.

How to maintain model integrity, peacefully ?

  • Bounded Context – The scope, the boundary, the shape of a model. A model is ...blah blah, no less, no more. Try to divide large model into small ones by isolate out independent contexts.
  • Continuous Integration – Why ? Because new insights emerge during model development, and they ruin your model integrity very fast.
    How? Fucking TEST IT !!! Remember... test the model invariants, not relationships
  • Context Map – Illustrate relationships between Bounded Contexts. 




Now, depicting the Context Map, then you get the big picture of your design




So how to do that ?

With principles like ...

Shared Kernel

Avoid duplicating works between 2 bounded contexts, extract the shared part, including model, code or even database design, into separated kernel module.

Customer-Supplier 

Could be mixed up with Shared Kernel but it's actually relationships between contexts. Customer acts as data-receiver, while Supplier as data-generator.
When this pattern emerges, customer team and supplier team should talk and talk and talk until all requirements and interfaces reach a consensus, then the 2 teams jointly build a test suite, which is for supplier team to work unreservedly afterwards.

Conformist

Which means in a customer-supplier relationship, the customer team totally embrace the rules, interfaces, models unilaterally provided by supplier team. If customer team wants to make any change on their side, a good choice is Adapter
Why happen ?
Because customer needs supplier, but not vice versa. From time to time, supplier team may lose its interest in this relationship and eventually doesn't give a shit. Or, it just not at the very beginning, like the most time when you use free 3rd-party libraries or services, you can't make requests to them, right ?

*Murmur warning*
In this section it takes lots of words to describe how a supplier tends to discard the relationship and no longer want to support it. In my personal experience I've seen how easily and many times it happens. I view it as a problem of Humanity, comes mostly from intrust and lack of communication or common goals or ...whatsoever. I mean, we try to solve some Humanity problem with a Technical strategy here, isn't it a bit awkward or ... not at the point maybe ?
The Supplier Team


Anticorruption Layer


Adapter, FaçadeTranslator, an their some other brothers live in this layer. The purpose is to Anti-our-models-corruption, which may comes from the requirements of exchanging data from external or legacy applications.

 How? A Façade for each external model, a Translator for data conversion, and an Adapter Service for wrapping the external model behavior.


Separate Ways

Rather than integrating subsystems due to relationships or common kernel, separating them could be better, because sometimes they just HAVE NOTHING TO DO WITH EACH OTHER
With reference to Interface Segregation in SOLID principles,
I tend to see Separate Ways as the extension of Interface Segregation Principle, they got similar purpose, quote from ISP's wiki :

ISP is intended to keep a system decoupled and thus easier to refactor, change, and redeploy

*Murmur warning*
Write here to remind myself. In many times I come up with a tempatation, to use a simplified model to deal with lots of logic in application, ex: "Hey~ eventually they all have wheels, why not just use a class called MeHaveWheels to represent them all", and I always regret this decision afterwards. They always have much more than wheels, like some GOD DAMN AI WHEELS, which take anything into the air and auto pilot, request by genius customers.
Separate Them

Open Host Service

When there is a Many(clients)-to-One(external) subsystem relationship. The translators between them become complex enough. Wrap them with Services, or even more, build some Protocol to handle all the translator logics.


Distillation

 ... to find the Core concept, make it the Core domain, anything else, refactor them into subdomains. Focus development on Core domain, as to subdomains, try to use external libraries or outsourcing.
What is the Core concept ? Let's say when we start trying to describe the domain model, we are studying the concept of "What they like", but meanwhile we may gradually dig up the deeper concept of "What they need", that "What they need" could very possibly become the Core concept, which turn out to be the most valuable part of our application.

Example (by a false story):

  • We were making a dating APP
  • At first we found the domain models are male, female, dating event, etc.
  • We implemented the models and dating process.
  • One year later ...
  • Our APP is selling condoms now.



No comments:

Post a Comment