Moje rozumienie DDD (+ clean architecture)

na przykładzie bajki o 3 świnkach​

Perspektywa... ma znaczenie :)

DDD


                        public interface DomainEntity<ID, T> {
                            T getSnapshot();
                        }
                    
                        public interface Aggregate<ID, T> extends DomainEntity<ID, T> {
                        }
                    
                        public interface EntitySnapshot<ID> {
                            ID id();
                        }
                    

                        public interface DomainRepository<ID, T extends Aggregate> {
                            Optional<T> findById(ID id);
                        
                            T save(T aggregate);
                        }
                    

                        public interface SnapshotWithEvents<ID> extends EntitySnapshot<ID> {
                            List<? extends DomainEvent> events();
                        }
                    
                        public interface EventsDrivenRepository<ID, T>> extends DomainRepository<ID, T> {
                            @Override
                            default T save(T aggregate) {
                                return append(aggregate.getSnapshot().events());
                            }
                        
                            T append(List<? extends DomainEvent> events);
                        }
                    

                            public interface DomainEvent {
                                Instant occurredOn();
                            }
                    
                        public interface DomainEventPublisher {
                            void publish(DomainEvent event);
                        }
                    

                        @FunctionalInterface
                        public interface Specification<T> extends Predicate<T> {
                            default boolean isSatisfiedBy(T objectToTest) {
                                return test(objectToTest);
                            }
                        }
                    

Clean Architecture

Założenia

  • Budowanie ze słomy, z drewna, z cegieł
  • Po zdmuchnięciu domu - ucieczka do sąsiedniego
  • Rezygnacja wilka po próbach zdmuchnięcia domu z cegieł
  • Przemilczane:
    • Wspinanie się wilka przez komin
    • Zjadanie świnek
    • Opowiadanie o mamie świnek
    • Imprezowanie świnek po skończonej pracy

Jaki agregat?

EventStorming!

  • Kilka poziomów szczegółowości
    • Big Picture
    • Process-Level
    • Design-Level
  • Złota zasada: zdarzenia (czasowniki), a nie struktury danych (rzeczowniki)
  • Rozróżnienie między komendami (zmieniają stan systemu), a widokami

Big Picture

Design-Level

User Story

  • Co system powinien "wystawić na świat"
  • Jako użytkownik, chcę poznać bajkę o trzech świnkach, żeby móc ją opowiedzieć innym
    • ...
  • Jako świnka, chcę zbudować dom, dostosowany do moich potrzeb
    • Dokumentowanie i utrwalanie informacji
  • Jako wilk chcę zdmuchnąć dom, żeby móc złapać świnkę
    • Rejestr - z tym domem już próbowano

Jak zbudować dom?


                            public class BigBadWolfService {
                                private final DomainEventPublisher eventPublisher;
                            
                                BigBadWolfService(final DomainEventPublisher eventPublisher) {
                                    this.eventPublisher = eventPublisher;
                                }
                            
                                public void blowDown(final House target) {
                                    try {
                                        target.handleHurricane();
                                    } catch (House.IndestructibleHouseException e) {
                                        retryBlowing(target);
                                    }
                                }
                            
                                private void retryBlowing(final House target) {
                                    try {
                                        target.handleHurricane();
                                    } catch (House.IndestructibleHouseException e) {
                                        eventPublisher.publish(
                                            new WolfResignedFromAttacking(target.getSnapshot().id())
                                        );
                                    }
                                }
                            }
                    

Specyfikacja - 1/2


                            class BlowingDownPossibility implements Specification<House> {
                                private final ConstructionSpecification strawConstruction 
                                                                = new ConstructionSpecification(STRAW);
                                private final ConstructionSpecification woodenConstruction 
                                                                = new ConstructionSpecification(WOOD);
                            
                                @Override
                                public boolean test(final House houseToTest) {
                                    return strawConstruction.or(woodenConstruction).test(houseToTest);
                                }
                            }
                            
                            class ConstructionSpecification implements Specification<House> {
                                private final Material originalResource;
                            
                                ConstructionSpecification(final Material material) {
                                    originalResource = material;
                                }
                            
                                @Override
                                public boolean test(final House houseToTest) {
                                    return originalResource == houseToTest.getSnapshot().material();
                                }
                            }
                    

Specyfikacja - 2/2


                            @Unroll('house from #inputMaterial vs. #resource')
                            def 'should fail for material different than in specification'() {
                                given:
                                def house = houseFrom inputMaterial

                                expect:
                                !new ConstructionSpecification(resource).isSatisfiedBy(house)

                                where:
                                resource | inputMaterial
                                STRAW    | BRICKS
                                WOOD     | STRAW
                                STRAW    | WOOD
                            }
                    

Overengineering?


                            WolfOnSteroidsSpecification {
                                boolean isSatisfiedBy(Wolf wolf) {
                                    return wolf.powerLevel() > 9000;
                                }
                            }
                    

                            @Before("handling()")
                            void logBeforeHandling(JoinPoint jp) {
                              if (logger.isInfoEnabled()) {
                                var command = jp.getArgs()[0];
                                if (command instanceof BuildHouse buildCommand) {
                                  switch (buildCommand.getOwner()) {
                                    case VERY_LAZY -> logger.info("The first little pig was very lazy");
                                    case LAZY -> logger.info("The second little pig was a bit more ambitious");
                                    case NOT_LAZY -> logger.info("The third little pig was ready for hard work");
                                  }
                                }
                              }
                            }
                    

Dzięki!