Skip to content

Latest commit

 

History

History

Folders and files

NameName
Last commit message
Last commit date

parent directory

..
 
 
 
 
 
 
 
 

AggregateRoot and Domain Events

With Spring Data (and Spring Data JDBC supports it as well) we can very easy implement some DDD concepts in a 1-2-3 steps

NOTE: this is not a really event sourced app

Aggregate Root is something we may update and due to DDD it can emit (Domain Eventsourced) events on different operations (so called commands)...

1

To make our Customer entity (from simple project) an aggregate is easy:

class Customer extends AbstractAggregateRoot<Customer> { /* ... */ }

NOTE: it's extends AbstractAggregateRoot with itself as a generic typed parameter...

2

Secondly (in our root aggregate) we need to register event, when something important from prespective our domain (business) is happened...

For erxample on create customer Command, each time when new customer was created, we will register customer created event, whuch eventually will be emitted by Spring Data after entity successfully persisted:

  public static Customer createForName(String name) {
    // Customer customer = ...
    CustomerCreatedEvent event = new CustomerCreatedEvent(customer);
    customer.registerEvent(event);
    return customer;
  }

where CustomerCreatedEvent definition is very simple:

import org.springframework.context.ApplicationEvent;

public class CustomerCreatedEvent extends ApplicationEvent {
  public CustomerCreatedEvent(Object source) {
    super(source);
  }
}

NOTE: it's simple spring application event

3

Lastly let's build statistics API to show how many customers with same name where actually created...

@RestController
public class StatisticsResource {

  Map<String, AtomicLong> statistics = new ConcurrentHashMap<>();

  @GetMapping("/statistics")
  public Map<String, AtomicLong> getStatistics() {
    return statistics;
  }

  @EventListener
  public void on(CustomerCreatedEvent event) {
    Customer newCustomer = (Customer) event.getSource();
    updateStatisticsFor(newCustomer);
  }

  void updateStatisticsFor(Customer newCustomer) {
    String name = newCustomer.getName();
    statistics.putIfAbsent(name, new AtomicLong(0));
    AtomicLong counter = statistics.get(name);
    counter.incrementAndGet();
    statistics.put(name, counter);
  }
}

build run and test

so lets's run app and after let's create few customers:

http :8002 name=test
http :8002 name=test
http :8002 name="test 2"

now we can check if statistics is working as expected:

http :8002/statistics
HTTP/1.1 200 OK
Content-Length: 21
Content-Type: application/json;charset=UTF-8
# output
{
    "test": 2,
    "test 2": 1
}

in addition

To make our app consistent after reboot, let's introduce statistics reconstruction in StatisticsResource class:

@RestController
public class StatisticsResource {

  private final CustomerRepository customerRepository;

  public StatisticsResource(CustomerRepository customerRepository) {
    this.customerRepository = customerRepository;
  }
  
  @PostConstruct
  public void reconstruct() {
    StreamSupport.stream(customerRepository.findAll().spliterator(), true)
                 .forEach(this::updateStatisticsFor);
  }
  // rest without caches...
}

And we are done ;)

TODO: additional resources to read