Wracając do mojego poprzedniego wpisu, naszła mnie pewna refleksja. Napisałem kiedyś czym różni się zdarzenie domenowe od integracyjnego opierając się o wpisy znalezione na blogach w Internecie. Teraz mam wrażenie, że jest jeszcze jeden typ. Nie wiem czy jest on gdziekolwiek opisany w literaturze. Chodzi mi o zdarzenia wewnątrzmodułowe, które “odkryłem” na bazie swoich doświadczeń. Wróćmy do przykładu z artykułu “Blokowanie optymistyczne a zdarzenia”.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
public Try<Occupation> occupy(
    BeneficiaryId beneficiaryId,
    ParkingSpotId parkingSpotId,
    SpotUnits spotUnits
) {
  return Try.of(() -> {
    if (!beneficiaryRepository.isPresent(beneficiaryId)) {
      throw new IllegalStateException("cannot find beneficiary with id " + beneficiaryId);
    }
    ParkingSpot parkingSpot = findBy(parkingSpotId);

    log.debug("occupying parking spot with id {} by beneficiary with id {}", parkingSpot.getParkingSpotId(), beneficiaryId);
    var result = parkingSpot.occupy(beneficiaryId, spotUnits);

    return Match(result).of(
        Case($Right($()), event -> {
          parkingSpotRepository.publish(event);
          return event.occupied().occupation();
        }),
        Case($Left($()), exception -> {
          throw exception;
        })
    );
  }).onFailure(exception -> log.error("cannot occupy parking spot, reason: {}", exception.getMessage()));
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
@Getter
@AllArgsConstructor
public class ParkingSpot {

  // ...

  public Either<RuntimeException, ParkingSpotOccupiedEvents> occupy(
      BeneficiaryId beneficiaryId,
      SpotUnits spotUnits
  ) {
    if (outOfUse) {
      return Either.left(new IllegalArgumentException("out of order"));
    }
    if (exceedsAllowedSpace(spotUnits)) {
      return Either.left(new IllegalArgumentException("not enough space"));
    }
    return Either.right(
        ParkingSpotOccupiedEvents.events(
            new ParkingSpotOccupied(
                parkingSpotId,
                Occupation.newOne(beneficiaryId, parkingSpotId, spotUnits),
                version)));
  }

}

Zdarzenie publikowane przez linijkę parkingSpotRepository.publish(event) zawiera w sobie wcześniej wspomnianą wersję na podstawie której dokonano walidacji. Nie chcę, aby ktokolwiek dowiedział się o takiej wewnętrznej strukturze zdarzenia. Ta struktura jest potrzebna w wybranym module, aby zapisać tylko te zmiany, które są istotne z punktu widzenia danej funkcjonalności biznesowej oraz, aby móc zaimplementować Optimistic Locking.

Może się jednak zdarzyć, że inny moduł chciałby wiedzieć, że dane miejsce postojowe zostało zajęte. Wtedy do gry niezbędne będzie dołączenie zdarzenia międzymodułowego.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@Override
public void publish(ParkingSpotEvent event) {
  if (event instanceof ParkingSpotEvent.ParkingSpotOccupied occupied) {
  	//... increment ParkingSpotEntity version (optimistic locking)

    InMemoryOccupationRepository.DATABASE.add(new OccupationEntity(
        occupied.occupation().getOccupationId().getValue(),
        occupied.occupation().getBeneficiaryId().getValue(),
        occupied.occupation().getParkingSpotId().getValue(),
        occupied.occupation().getSpotUnits().getValue()));

    eventPublisher.publish(new ParkingSpotOccupied(
    	occupied.occupation().getBeneficiaryId(),
        occupied.occupation().getParkingSpotId(),
        occupied.occupation().getSpotUnits()))
  }
  //... else ifs
}

Powyżej mamy implementację tego przypadku dla bazy in memory. Schemat jest taki:

  • Podbijamy wersję encji ParkingSpotEntity o ile jest ona taka sama w zdarzeniu jak i bazie danych
  • Dodajemy nową “zajętość” miejsca postojowego
  • Publikujemy zdarzenie międzymodułowe/integracyjne

Co o tym myślisz? Ma to sens? Mocno się nad tym zastanawiałem i nie potrafiłem z tego inaczej wybrnąć. Nie wiem czy to nie nadmierna komplikacja i da się zrobić to prościej.