IN SEARCH OF DOMAIN DRIVEN DESIGN

IN THE WORLD OF
MICROSERVICES

WHO AM I?

KZ

Anuar Nurmakanov

ENGX AND XP PRACTICES

BELIEVE IN AGILE

AND LOVE JAVA

WE STARTED WORKING ON GREEN FIELD PROJECT

INSURANCE BUSINESS

SOME WANTED TO USE MICROSERVICES

SOME ALREADY FACE TANGLED MICROSERVICES PROBLEM

EVERYONE GOT CRAZY ABOUT MICROSERVICES

MONOLITH TO MICROSERVICES

IT HIPSTERS

EVERYBODY WANTS TO BE LIKE

ARE YOU BIG AS NETFLIX?

WHAT IS "MICROSERVICE"?

MICROSERVICES IS SUBSET OF SERVICE ORIENTED ARCHITECTURE

IT IS DISTRIBUTED SYSTEM IN THE END

WE DREAM ABOUT IDEAL MICROSERVICES ARCHITECTURE

WHAT IS IDEAL MICROSERVICES ARCHITECTURE?

WHEN WE HAVE RIGHT BOUNDARIES FOR EVERY SINGLE MICROSERVICE

HOW CAN WE FIND MICROSERVICES BOUNDARIES

WHERE EVERY MICROSERVICE STARTS AND ENDS?

MANY STRATEGIES

  1. LINES OF CODE

  2. NUMBER OF ENDPOINTS

  3. ONE ENTITY

  4. ONE AGGREGATE

  5. ONE PROCESS

  6. ETC.

TOO MANY MICROSERVICES

ENORMOUS AMOUNT OF MICROSERVICES

AND WHAT WE HAVE IS FRANKENSTEIN MICROSERVICES

HOW CAN WE PREVENT IT?

TYPICAL DOMAIN

INSURANCE

ACCOUNTS

MEDICAL

FINANCE

PROPERTY VALUATION

MONOLITH?

MONOLITH

MANY SERVICES?

HOW CAN WE MAKE A DECISION?

TRY TO FIND BOUNDARIES

martinfowler.com

WHAT IS BUSINESS CAPABILITY?

HOW CAN WE FIND THEM?

THERE IS ONE IDEA

DOMAIN DRIVEN DESIGN

WHAT IS DOMAIN DRIVEN DESIGN?

DOMAIN

IT IS REALITY OF OUR BUSINESS

REALITY

ABSTRACTION

UBIQUITOUS LANGUAGE 

business

development team

Guys, can you add documents search for back-off users?

I do not see documents in the system, only files...

We have documents functionality, but we use files  term

How I could know this?

#documents

#files

AND YOU JUST FEEL LIKE

UBIQUITOUS LANGUAGE

UBIQUITOUS LANGUAGE

HOW MANY DIALECTS CAN WE HAVE?

INSURANCE COMPANY STRUCTURE

INSURANCE DEPARTMENT

ACCOUNTS
DEPARTMENT

MEDICAL DEPARTMENT

SOMEBODY WANTS TO GET OUR INSURANCE

INSURED

PERSONA

PATIENT

3 UBIQUITOUS LANGUAGES

HOW WE USE DDD

START WORKING ON DOMAIN MODEL

BUSINESS

DEVELOPMENT
TEAM

DOMAIN MODEL

REVIEW DOMAIN MODEL

development team

business

SCHEDULED ACTIVITY

KEEP IT IN USER STORIES

DOMAIN MODEL

AS A BROKER WHEN I SEE THE LIST OF POLICIES I WANT TO BE ABLE TO DISCARD A POLICY SO I CAN KEEP MY LIST OF POLICIES CLEAN

KEEP IT IN TESTS

DOMAIN MODEL

broker _ShouldBeAble_To DiscardPolicies _WhenHeSeesListOf Policies

KEEP IT IN DIAGRAMS

DOMAIN MODEL

C4 MODEL

  • CONTEXT

  • CONTAINERS

  • COMPONENTS

  • CLASSES

KEEP IT IN SOURCE CODE


public void discardPolicies(
        Set<Policy> policies, 
        long brokerId) {
    val broker = this.brokerRepository.findBy(brokerId);
    this.broker.discardPolicies(policies);
}
public class Broker {

    public void discardPolicies(Set<Policy> policies) {
        //some domain logic here
        ...
        //
    }
}

HOW DDD HELPS WITH BOUNDARIES?

UBIQUITOUS LANGUAGE/DIALECT

BOUNDED CONTEXT

BOUNDED CONTEXT IS CORE OF DOMAIN DRIVEN DESIGN

IF YOU USE TACTICAL DESIGN IT DOES NOT MEAN THAT YOU FOLLOW DDD

BOUNDED CONTEXT IN OUR DOMAIN

  • DIFFERENT STAKEHOLDERS

  • DIFFERENT DELIVERY REQUIREMENTS

  • DIFFERENT DOMAIN LANGUAGES DIALECTS

  • DIFFERENT TEAMS

DECOMPOSE TO BOUNDED CONTEXTS

BOUNDED CONTEXT

BOUNDED CONTEXT

BOUNDED CONTEXT

INSURANCE SERVICE

ACCOUNTS  SERVICE

MEDICAL SERVICE

DO WE NEED TO GO FURTHER IF WE DON'T HAVE REASONS?

REASONS

WHAT ELSE DDD OFFERS

AGGREGATE

AGGREGATES

WHY NOT?

TRANSACTIONAL BOUNDARIES

DECOMPOSE TO AGGREGATES

BOUNDED CONTEXT

DECOMPOSE TO AGGREGATES

BOUNDED CONTEXT

DECOMPOSE TO AGGREGATES

LET'S SAY THAT WE CAN HAVE MANY MICROSERVICES

DO WE NEED MICROSERVICES?

MONOLITH FIRST

MICROSERIVES ARE COMPLEX

SERVICE DISCOVERY

CONFIG
MANAGEMENT

CONTAINERS
MANAGEMENT

MONITORING

FAILURE
RECOVERY

KEEP BOUNDARIES INSIDE MONOLITH

MONOLITH

WHEN TIME COMES TO SPLIT

MONOLITH

WHEN TIME COMES TO SPLIT

OLD MONOLITH

CORE SERVICE

EXTRACTED MEDICAL SERVICE

EXTRACTED ACCOUNTANCY SERVICE

IT'S WHY YOU MUST KNOW HOW TO KEEP THESE BOUNDARIES

HOW TO KEEP BOUNDARIES?

CLEAN ARCHITECTURE

WE DON'T WANT TO HAVE BIG BALL OF MUD

ROBERT MARTIN

IT IS EASY TO READ AND GET DETAILS

BOUNDED CONTEXT OR AGGREGATE = MODULE

BOUNDED CONTEXTS

AS A GROUP OF COMPONENTS

AGGREGATE  AS A COMPONENT

USE HEXAGONAL ARCHITECTURE

EVERY MODULE IS A HEXAGON AND MODULES CAN INTERACT ONLY THROUGH BOUNDARIES

IF WE WANT TO SPLIT THEM, WHERE WE CAN HAVE TIGHT COUPLING?

POSSIBLE TIGHT COUPLING PLACES

  • BOUNDARY (ENDPOINTS)

  • CONTROL (CONNECTORS)

  • ENTITY (DATA + BUSINESS LOGIC)

BOUNDARY

@AllArgsConstructor
@RestController
public class InsuredAdultsEndpoint {
    private final SearchInsuredPersonService service;

    @GetMapping("/insuredadults")
    List<InsuredAdultAggregate> findAll() {
        return service.findAll();
    }
    ...
}

LOOKS LIKE IT IS NOT WHERE WE CAN HAVE IT

CONTROL

@AllArgsConstructor
@Service
public class SearchInsuredPersonService {
    private final BrokersService brokersService;
    private final InsuredPersonRepository repository;

    public InsuredPersonAggregate findById(long insuredPersonId) {
        var insured = this.repository.findById(insuredPersonId);
        this.insured.ifPresent(
            ins-> ins.fetchBrokerInfo(brokersService)
        );
        ...
    }
}

LOOKS LIKE IT IS NOT WHERE WE CAN HAVE IT

ENTITY

@Entity
@Data
@NoArgsConstructor
public class InsuredPersonaAggregate {
    @Embedded
    private Name name;
    private Company company;
    private ApplicationForm applicationForm;

    @OneToOne
    @JoinColumn(name = "broker_info_id")
    private BrokerInfoAggregate brokerInfo;
    
    //domain business logic is here
    
    public void changeAddress(Address address) {
        //...
        //
    }

    public void changeName(Address address) {
        //...
        //
    }
}

DO NOT USE DIRECT REFERENCES

WE WISH TO KEEP THEM AS INDEPENDENT MODULES

DIRECT REFERENCE EXAMPLE

@Entity
@Data
@NoArgsConstructor
public class InsuredPersonaAggregate {
    @Embedded
    private Name name;
    private Company company;
    private ApplicationForm applicationForm;

    @OneToOne
    @JoinColumn(name = "broker_info_id")
    private BrokerInfoAggregate brokerInfo;
    
    //domain business logic is here
    
    public void changeAddress(Address address) {
        //...
        //
    }

    public void changeName(Address address) {
        //...
        //
    }
}

DIRECT REFERENCE CONS

  • TIGHT COUPLING

  • TRANSACTION BOUNDARIES ARE VAGUE

  • CHANCES TO MODIFY SECOND AGGREGATE WHEN THE FIRST IS CHANGED

EVEN IF WE HAVE LAZY LOAD

ID AS VALUE OBJECT

@Entity
@Data
@NoArgsConstructor
public class InsuredPersonAggregate {
    @Embedded
    private Name name;
    private Company company;
    private ApplicationForm applicationForm;

    @Embedded
    private BrokerInfoId brokerInfoId;
    ...
}
@Data
@Embeddable
@NoArgsConstructor
public class BrokerInfoId {
    @Column(name = "broker_info_id")
    private long id;
}

SO WE DID EVERYTHING HOW IT WAS RECOMMENDED AND WE WANT TO SPLIT OUR SERVICES FURTHER

SPLITTING STRATEGY

SPLIT COMPONENTS

BROKERS

SERVICE

DOCUMENTS SERVICE

PAYMENTS
SERVICE

POLICIES
SERVICE

SPLIT COMPONENTS

AND ETC...

STRANGLER PATTERN

from microsoft.com

BROKERS

DOCUMENTS

PAYMENTS

POLICIES SERVICE

API GATEWAY

server.port=8080
eureka.client.serviceUrl.defaultZone = http://localhost:8010/eureka
spring.application.name=ApiGateway
spring.cloud.gateway.discovery.locator.enabled=true
spring.cloud.gateway.discovery.locator.lower-case-service-id=true

spring.cloud.gateway.routes[0].id=policies
spring.cloud.gateway.routes[0].uri=lb://INSURANCE-MONOLITH/policies
spring.cloud.gateway.routes[0].predicates[0]=Path=/policies/**
...


spring.cloud.gateway.routes[0].id=documents
spring.cloud.gateway.routes[0].uri=lb://INSURANCE-MONOLITH/documents
spring.cloud.gateway.routes[0].predicates[0]=Path=/documents/**
...

spring.cloud.gateway.routes[0].id=brokers
spring.cloud.gateway.routes[0].uri=lb://INSURANCE-MONOLITH/brokers
spring.cloud.gateway.routes[0].predicates[0]=Path=/brokers/**
...

spring.cloud.gateway.routes[0].id=payments
spring.cloud.gateway.routes[0].uri=lb://INSURANCE-MONOLITH/payments
spring.cloud.gateway.routes[0].predicates[0]=Path=/payments/**

BROKERS

DOCUMENTS

PAYMENTS

POLICIES

API GATEWAY

server.port=8080
eureka.client.serviceUrl.defaultZone = http://localhost:8010/eureka
spring.application.name=ApiGateway
spring.cloud.gateway.discovery.locator.enabled=true
spring.cloud.gateway.discovery.locator.lower-case-service-id=true

spring.cloud.gateway.routes[0].id=policies
spring.cloud.gateway.routes[0].uri=lb://INSURANCE-POLICIES/policies
spring.cloud.gateway.routes[0].predicates[0]=Path=/policies/**
...


spring.cloud.gateway.routes[0].id=documents
spring.cloud.gateway.routes[0].uri=lb://INSURANCE-DOCUMENTS/documents
spring.cloud.gateway.routes[0].predicates[0]=Path=/documents/**
...

spring.cloud.gateway.routes[0].id=brokers
spring.cloud.gateway.routes[0].uri=lb://INSURANCE-MONOLITH/brokers
spring.cloud.gateway.routes[0].predicates[0]=Path=/brokers/**
...

spring.cloud.gateway.routes[0].id=payments
spring.cloud.gateway.routes[0].uri=lb://INSURANCE-MONOLITH/payments
spring.cloud.gateway.routes[0].predicates[0]=Path=/payments/**

CHANGE CHANNELS

  • THE SAME BUILDING ARTIFACT

  • COMMUNICATE THROUGH INTERFACES

RIGHT COMMUNICATION BETWEEN MODULES

@AllArgsConstructor
@Service
public class PoliciesSubmitService {
    private final BrokersSearchService brokersSearchService;
    private final PolicyRepository repository;

    public void submitPolicy(long policyId, long brokerId) {
        var broker = this.brokersSearchService.findBy(brokerId);

        this.repository.findById(policyId).ifPresent(
                policy -> policy.submitTo(broker)
        );
    }
}

WRONG COMMUNICATION BETWEEN MODULES

@AllArgsConstructor
@Service
public class PoliciesSubmitService {
    private final BrokerRepository brokerRepository;
    private final PolicyRepository repository;

    public void submitPolicy(long policyId, long brokerId) {
        var broker = this.brokerRepository.findBy(brokerId);

        this.repository.findById(policyId).ifPresent(
                policy -> policy.submitTo(broker)
        );
    }
}

CHANGE CHANNELS

  • SYNC - STRONG CONSISTENCY

  • ASYNC - EVENTUAL CONSISTENCY

NEW CHANNELS

@AllArgsConstructor
@Service
public class PoliciesSubmitService {
    private final BrokerClient brokerClient;
    private final PolicyRepository repository;

    public void submitPolicy(long policyId, long brokerId) {
        var broker = this.brokerClient.findBy(brokerId);
        this.repository.findById(policyId).ifPresent(
                policy -> policy.submitTo(broker)
        );
    }
}
@FeignClient(name="BrokersService" )
public interface BrokerClient {
    @RequestMapping("/brokers/{id}")
    Broker findById(@Param("id") Long id);
 
     @RequestMapping("/brokers")
    List<Broker> findAll();
}

SUMMARY

  • WE MUST LEARN FROM DOMAIN EXPERTS

  • UBIQUITOUS LANGUAGE IS CRUCIAL

  • BOUNDED CONTEXTS HELP WITH MICROSERVICES

  • AGGREGATES IS THE NEXT STEP

  • KEEP BOUNDARIES IN ADVANCE

  • SPLIT WHEN IT IS NEEDED

QA TIME