Agregat DDD do rezerwacji biletów

Agregat DDD do rezerwacji biletów
NO
  • Rejestracja:ponad 2 lata
  • Ostatnio:2 dni
  • Postów:135
0

Co sądzicie o tym agregacie (DDD) do rezerwowania i anulowania biletów? Jest ok, czy jakoś inaczej byście go zrobili?

Kopiuj
@Entity
@Table(name = "screenings")
@Getter
@ToString(exclude = "tickets")
public class Screening {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private LocalDateTime date;

    private Long filmId;

    private Long hallId;

    @OneToMany
    private List<Ticket> tickets;

    protected Screening() {}

    public Screening(LocalDateTime date, Long filmId, Long hallId
    ) {
        this.date = date;
        this.filmId = filmId;
        this.hallId = hallId;
        this.tickets = new ArrayList<>();
    }

    public void bookTickets(List<Ticket> tickets, TicketBookingPolicy ticketBookingPolicy) {
        ticketBookingPolicy.checkScreeningDate(this.date);
        tickets.forEach(ticket -> {
            if (this.tickets.contains(ticket)) {
                throw new TicketAlreadyExistsException();
            }
        });
        this.tickets.addAll(tickets);
    }

    public Ticket cancelTicket(Long ticketId, TicketCancellingPolicy ticketCancellingPolicy) {
        ticketCancellingPolicy.checkScreeningDate(this.date);
        var foundTicket = tickets
                .stream()
                .filter(ticket -> ticket.getId().equals(ticketId))
                .findFirst()
                .orElseThrow(TicketNotFoundException::new);
        foundTicket.cancel();
        return foundTicket;
    }
}

Jeden z problemów jaki widzę na razie, to to że przy anulowaniu trzeba się przeiterować po całej kolekcji biletów, ale ta kolekcja nie będzie duża, w najgorszym wypadku ok 100 rekordów.

edytowany 2x, ostatnio: Nofenak
MU
  • Rejestracja:prawie 8 lat
  • Ostatnio:około 13 godzin
  • Postów:89
1

Po pierwsze to ja bym zrobił pojedyncze addTicket i zmusił klienta do dodawania ticketów pojedynczo, a nie listę. Lepiej wywalić błąd na jednym tickecie niż mieć problem z całym screeningien, bo jakiś jeden już istnieje. To problem klienta, że dodaje zduplikowany bilet, ale niech wie jaki i niech tylko ten jeden będzie problemem, a nie cala kolekcja.

Anulowanie możesz napisać pod spodem implementację na jedną query do bazy, więc obejdzie się bez iteracji. Przykryjesz to ładną metodą cancelScreening i wsio.

Ale autorytetem w DDD nie jestem więc niech inni się wypowiedzą.

edytowany 1x, ostatnio: musrus
markone_dev
  • Rejestracja:około 3 lata
  • Ostatnio:około 10 godzin
  • Postów:816
0

A jakie jest wymaganie biznesowe? Bo od tego zaczyna się zabawa w modelowanie domeny na poziomie wzorców taktycznych. Robiłeś jakiś event storming, modelling whirpool, albo jakikolwiek inny proces do eksploracji domeny, czy tak sobie wesoło klepiesz co tam Ci ślina na język przyniesie?


Programujący korpo architekt chmurowy.
Udzielam konsultacji i szkoleń w obszarze szeroko pojętego cloud computingu (Azure, AWS) i architektury systemów IT. Dla firm i prywatnie.
DevOps to proces nie stanowisko.
NO
  • Rejestracja:ponad 2 lata
  • Ostatnio:2 dni
  • Postów:135
0
markone_dev napisał(a):

A jakie jest wymaganie biznesowe? Bo od tego zaczyna się zabawa w modelowanie domeny na poziomie wzorców taktycznych. Robiłeś jakiś event storming, modelling whirpool, albo jakikolwiek inny proces do eksploracji domeny, czy tak sobie wesoło klepiesz co tam Ci ślina na język przyniesie?

Wymagania biznesowe:

  1. Użytkownik może rezerwować wiele biletów
  2. Rezerwować bilety można najpóźniej godzinę przed seansem
  3. Może istnieć tylko jeden bilet na dane miejsce
  4. Anulować bilet można najpóźniej 24h przed seansem
LukeJL
  • Rejestracja:około 11 lat
  • Ostatnio:mniej niż minuta
  • Postów:8409
0
Nofenak napisał(a):
    ticketCancellingPolicy.checkScreeningDate(this.date);

Nigdzie tego nie przypisujesz ani nie używasz w innym miejscu. Jak to więc działa? Czy checkScreeningDate rzuca wyjątkiem, jeśli data jest nie halo?


NO
  • Rejestracja:ponad 2 lata
  • Ostatnio:2 dni
  • Postów:135
0
LukeJL napisał(a):
Nofenak napisał(a):
    ticketCancellingPolicy.checkScreeningDate(this.date);

Nigdzie tego nie przypisujesz ani nie używasz w innym miejscu. Jak to więc działa? Czy checkScreeningDate rzuca wyjątkiem, jeśli data jest nie halo?

Dokładnie tak

markone_dev
  • Rejestracja:około 3 lata
  • Ostatnio:około 10 godzin
  • Postów:816
1
Nofenak napisał(a):

Wymagania biznesowe:

  1. Użytkownik może rezerwować wiele biletów
  2. Rezerwować bilety można najpóźniej godzinę przed seansem
  3. Może istnieć tylko jeden bilet na dane miejsce
  4. Anulować bilet można najpóźniej 24h przed seansem

Ja tu poletka do DDD nie widzę, ale na siłę mogłoby to wyglądać tak (C#).

Primo mieszasz pojęcia biletu z miejscem na sali kinowej. Ja na potrzeby przykładu przyjąłem, że mówimy o miejscach nie o biletach, bo bilet to szczegół sprzedażowy. Nie znam się na systemach obsługi kin, ale mogę sobie wyobrazić sytuację że na jednym bilecie masz kilka miejsc.

Secundo przyjąłem założenie że ilość dostępnych miejsc może się różnić w zależności od seansu. Wtedy masz Screening (seans), który wie ile miejsc jest dostępnych i to on jest agregatem przy rezerwacji miejsc i sprawdza czy miejsce jest dostępne czy nie. Chociaż tak jak napisałem jest to zrobione na siłę bo przy tej logice to nie trzeba się bawić w agregaty.

Kopiuj
internal class Screening : AggregateRoot
{
    public ScreeningId Id { get; }
    public DateTime StartsOn { get; }
    public DateTime EndsOn { get; }
    public IEnumerable<Seat> Seats => _seats;

    public Result BookSeat(Seat seat, CustomerId customerId)
    {
        if (IsLateBooking())
            return Result.Failure("Tickets can be booked no later than one hour before the show");

        if (!IsSeatAvailable(seat.Number))
            return Result.Failure($"Selected seat ({seat.Number}) is booked already");

        seatToBook.Reserve(customerId);

        AddEvent(new SeatBooked(
            screeningId: Id.Value,
            seatNumber: seat.Number,
            customerId: customerId.Value);

        return Result.Success($"A seat with number ({seat.Number}) is booked successfully.");
    }

    public Result FreeUpSeat()
    {
        // TODO
    }

    private bool IsLateBooking()
    {
        // TODO
    }

    private bool IsSeatAvailable(int seatNumber)
    {
        var seatToBook = _seats.Find(s => s.Number == seatNumber);
        return seatToBook.IsAvailable; //may have more complex logic, enough for the demo, lol
    }
}

Po walidacji reguł biznesowych które mogą być w agregacie, albo zewnętrznej polityce w zależności od tego jak często mogą się one zmieniać następuje rezerwacja miejsca seatToBook.Reserve() po czym następuje zarejestrowanie zdarzenia, które wywoła handler SeatBookedHandler który to utworzy w systemie rezerwację (być może w rzeczywistym systemie rezerwacja to osobna poddziedzina albo bounded context. Być może na jednej rezerwacji powinno być kilka miejsc per klient? Nie wiem, zmyślam.

Kopiuj
internal class SeatBookedHandler : IEventHandler<SeatBooked>
{
    private readonly IReservationRepository _reservationRepository;

    public SeatBookedHandler(IReservationRepository reservationRepository)
    {
        _reservationRepository = reservationRepository;
    }

    public Result Handle(SeatBooked @event)
    {
        var reservation = new Reservation(
            screeningId: ScreeningId.FromValue(@event.ScreeningId),
            customerId: CustomerId.FromValue(@event.CustomerId),
            seatNumber: @event.SeatNumber);

        _reservationRepository.Save(reservation);

        return Result.Success("Reservation created.");

    }
}

Programujący korpo architekt chmurowy.
Udzielam konsultacji i szkoleń w obszarze szeroko pojętego cloud computingu (Azure, AWS) i architektury systemów IT. Dla firm i prywatnie.
DevOps to proces nie stanowisko.
99xmarcin
  • Rejestracja:prawie 5 lat
  • Ostatnio:5 miesięcy
  • Postów:2420
0

Ja bym zrobił inaczej - pojedyńczy ticket jest agregatem. Operacja na pojedyńczym ticketcie powinna sie zamykać w jednej transakcji: to jest utworzenie biletu, rezerwacja, zapłata, anulowanie. Dwa bilety na to samo wydarzenie powinno sie dać rezerwować w tym samym czasie.

No ale ja jestem skażony praktyką, wiem że jak ten agregat będzie miał 50k biletów to DB co to trzyma pierdyknie...


Holy sh*t, with every month serenityos.org gets better & better...
MU
  • Rejestracja:prawie 8 lat
  • Ostatnio:około 13 godzin
  • Postów:89
0
0xmarcin napisał(a):

Ja bym zrobił inaczej - pojedyńczy ticket jest agregatem. Operacja na pojedyńczym ticketcie powinna sie zamykać w jednej transakcji: to jest utworzenie biletu, rezerwacja, zapłata, anulowanie.

Ten agregat nie bierze pod uwagę dostępnych biletów / miejsc

edytowany 1x, ostatnio: musrus
99xmarcin
  • Rejestracja:prawie 5 lat
  • Ostatnio:5 miesięcy
  • Postów:2420
0

Zarządzanie dostępnymi miejscami to inny problem. Można go rozwiązać na 100 sposobów np. kolejkami - rezerwacaj bierze następny ticket w kolejce, zwraca na sam koniec. Także wybór wolnego miejsca sprawadza się do pewnego query, nie ma nic wspólnego z transakcją więc nie musi być wciskana w agregat.


Holy sh*t, with every month serenityos.org gets better & better...
Black007
  • Rejestracja:ponad 21 lat
  • Ostatnio:dzień
0

Zgadzam się z przedmówcą

Operacja na pojedyńczym ticketcie powinna sie zamykać w jednej transakcji: to jest utworzenie biletu, rezerwacja, zapłata, anulowanie. Dwa bilety na to samo wydarzenie powinno sie dać rezerwować w tym samym czasie.

Ja bym się zastanowił nad tym czemu ten agregat (część domeny) jest encją bazodanową?
Baza danych to jest infrastruktura, tutaj Ci przeciekają warstwy...
Ja bym oddzielił model biznesowy od danych zapisywanych w repo. Dzięki temu mogę decydować jak chcę to zapisać (mongo, postgres, a może kafka?)


"Nie popełnia błędów tylko ten, kto nic nie robi"
99xmarcin
  • Rejestracja:prawie 5 lat
  • Ostatnio:5 miesięcy
  • Postów:2420
0

Każda abstrakcja cieknie, trzeba myśleć i o DDD i o tym jak to będzie pod spodem działało. Łatwo wymienić Postgres'a na MariaDB ale już ciężko przejść z Mongo na SQLa


Holy sh*t, with every month serenityos.org gets better & better...
SZ
  • Rejestracja:prawie 2 lata
  • Ostatnio:3 miesiące
  • Postów:52
0

To może nie od strony ddd. Użytkownik wchodzi na stronę kina. Wybiera kino, seans, widzi widok sali ewentualnie aktualny. Wybiera wolne miejsca. Front nie pozwala wybrać więcej niż 1 raz tego samego miejsca. Skąd pochodzi widok wolnych i zajętych krzeseł dla danego kina i seansu? Pewnie z materialized view (co by te joiny zamieść pod dywan, no i to query musi cykać a nie dzwonić). I teraz każda transakcja kończy się zapisaniem rekordu do bazy key-value, gdzie key to hash (cinemaIDscreaningIDhallIDseatID) a value to boolean. Jednym słowem każda transakcja kończy się sprawdzeniem value, lock, update, unlock 1 rekordu. Dla 100 klientów to pewnie bezsensu. Dla takiego Multikina jak znalazł. Ticket to sprawa poboczna jako etap realizacji skutecznej transakcji.

Kliknij, aby dodać treść...

Pomoc 1.18.8

Typografia

Edytor obsługuje składnie Markdown, w której pojedynczy akcent *kursywa* oraz _kursywa_ to pochylenie. Z kolei podwójny akcent **pogrubienie** oraz __pogrubienie__ to pogrubienie. Dodanie znaczników ~~strike~~ to przekreślenie.

Możesz dodać formatowanie komendami , , oraz .

Ponieważ dekoracja podkreślenia jest przeznaczona na linki, markdown nie zawiera specjalnej składni dla podkreślenia. Dlatego by dodać podkreślenie, użyj <u>underline</u>.

Komendy formatujące reagują na skróty klawiszowe: Ctrl+B, Ctrl+I, Ctrl+U oraz Ctrl+S.

Linki

By dodać link w edytorze użyj komendy lub użyj składni [title](link). URL umieszczony w linku lub nawet URL umieszczony bezpośrednio w tekście będzie aktywny i klikalny.

Jeżeli chcesz, możesz samodzielnie dodać link: <a href="link">title</a>.

Wewnętrzne odnośniki

Możesz umieścić odnośnik do wewnętrznej podstrony, używając następującej składni: [[Delphi/Kompendium]] lub [[Delphi/Kompendium|kliknij, aby przejść do kompendium]]. Odnośniki mogą prowadzić do Forum 4programmers.net lub np. do Kompendium.

Wspomnienia użytkowników

By wspomnieć użytkownika forum, wpisz w formularzu znak @. Zobaczysz okienko samouzupełniające nazwy użytkowników. Samouzupełnienie dobierze odpowiedni format wspomnienia, zależnie od tego czy w nazwie użytkownika znajduje się spacja.

Znaczniki HTML

Dozwolone jest używanie niektórych znaczników HTML: <a>, <b>, <i>, <kbd>, <del>, <strong>, <dfn>, <pre>, <blockquote>, <hr/>, <sub>, <sup> oraz <img/>.

Skróty klawiszowe

Dodaj kombinację klawiszy komendą notacji klawiszy lub skrótem klawiszowym Alt+K.

Reprezentuj kombinacje klawiszowe używając taga <kbd>. Oddziel od siebie klawisze znakiem plus, np <kbd>Alt+Tab</kbd>.

Indeks górny oraz dolny

Przykład: wpisując H<sub>2</sub>O i m<sup>2</sup> otrzymasz: H2O i m2.

Składnia Tex

By precyzyjnie wyrazić działanie matematyczne, użyj składni Tex.

<tex>arcctg(x) = argtan(\frac{1}{x}) = arcsin(\frac{1}{\sqrt{1+x^2}})</tex>

Kod źródłowy

Krótkie fragmenty kodu

Wszelkie jednolinijkowe instrukcje języka programowania powinny być zawarte pomiędzy obróconymi apostrofami: `kod instrukcji` lub ``console.log(`string`);``.

Kod wielolinijkowy

Dodaj fragment kodu komendą . Fragmenty kodu zajmujące całą lub więcej linijek powinny być umieszczone w wielolinijkowym fragmencie kodu. Znaczniki ``` lub ~~~ umożliwiają kolorowanie różnych języków programowania. Możemy nadać nazwę języka programowania używając auto-uzupełnienia, kod został pokolorowany używając konkretnych ustawień kolorowania składni:

```javascript
document.write('Hello World');
```

Możesz zaznaczyć również już wklejony kod w edytorze, i użyć komendy  by zamienić go w kod. Użyj kombinacji Ctrl+`, by dodać fragment kodu bez oznaczników języka.

Tabelki

Dodaj przykładową tabelkę używając komendy . Przykładowa tabelka składa się z dwóch kolumn, nagłówka i jednego wiersza.

Wygeneruj tabelkę na podstawie szablonu. Oddziel komórki separatorem ; lub |, a następnie zaznacz szablonu.

nazwisko;dziedzina;odkrycie
Pitagoras;mathematics;Pythagorean Theorem
Albert Einstein;physics;General Relativity
Marie Curie, Pierre Curie;chemistry;Radium, Polonium

Użyj komendy by zamienić zaznaczony szablon na tabelkę Markdown.

Lista uporządkowana i nieuporządkowana

Możliwe jest tworzenie listy numerowanych oraz wypunktowanych. Wystarczy, że pierwszym znakiem linii będzie * lub - dla listy nieuporządkowanej oraz 1. dla listy uporządkowanej.

Użyj komendy by dodać listę uporządkowaną.

1. Lista numerowana
2. Lista numerowana

Użyj komendy by dodać listę nieuporządkowaną.

* Lista wypunktowana
* Lista wypunktowana
** Lista wypunktowana (drugi poziom)

Składnia Markdown

Edytor obsługuje składnię Markdown, która składa się ze znaków specjalnych. Dostępne komendy, jak formatowanie , dodanie tabelki lub fragmentu kodu są w pewnym sensie świadome otaczającej jej składni, i postarają się unikać uszkodzenia jej.

Dla przykładu, używając tylko dostępnych komend, nie możemy dodać formatowania pogrubienia do kodu wielolinijkowego, albo dodać listy do tabelki - mogłoby to doprowadzić do uszkodzenia składni.

W pewnych odosobnionych przypadkach brak nowej linii przed elementami markdown również mógłby uszkodzić składnie, dlatego edytor dodaje brakujące nowe linie. Dla przykładu, dodanie formatowania pochylenia zaraz po tabelce, mogłoby zostać błędne zinterpretowane, więc edytor doda oddzielającą nową linię pomiędzy tabelką, a pochyleniem.

Skróty klawiszowe

Skróty formatujące, kiedy w edytorze znajduje się pojedynczy kursor, wstawiają sformatowany tekst przykładowy. Jeśli w edytorze znajduje się zaznaczenie (słowo, linijka, paragraf), wtedy zaznaczenie zostaje sformatowane.

  • Ctrl+B - dodaj pogrubienie lub pogrub zaznaczenie
  • Ctrl+I - dodaj pochylenie lub pochyl zaznaczenie
  • Ctrl+U - dodaj podkreślenie lub podkreśl zaznaczenie
  • Ctrl+S - dodaj przekreślenie lub przekreśl zaznaczenie

Notacja Klawiszy

  • Alt+K - dodaj notację klawiszy

Fragment kodu bez oznacznika

  • Alt+C - dodaj pusty fragment kodu

Skróty operujące na kodzie i linijkach:

  • Alt+L - zaznaczenie całej linii
  • Alt+, Alt+ - przeniesienie linijki w której znajduje się kursor w górę/dół.
  • Tab/⌘+] - dodaj wcięcie (wcięcie w prawo)
  • Shit+Tab/⌘+[ - usunięcie wcięcia (wycięcie w lewo)

Dodawanie postów:

  • Ctrl+Enter - dodaj post
  • ⌘+Enter - dodaj post (MacOS)