Giter Site home page Giter Site logo

ddd-by-examples / library Goto Github PK

View Code? Open in Web Editor NEW
4.1K 199.0 658.0 46.9 MB

A comprehensive Domain-Driven Design example with problem space strategic analysis and various tactical patterns.

License: MIT License

Java 55.30% Groovy 44.64% Dockerfile 0.06%
domain-driven-design ddd ddd-architecture c4 hexagonal-architecture ports-and-adapters crud aggregate aggregate-root functions

library's People

Contributors

ajurasz avatar amirdt22 avatar bslota avatar delor avatar ghisvail avatar jakzal avatar jklata avatar krzykrucz avatar lukaszkostrzewa avatar marcinswierczynski avatar marciovmartins avatar mszarlinski avatar paulczar avatar pillopl avatar pszymczyk avatar wyhasany avatar ziebamarcin avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

library's Issues

GroovyTemplateAutoConfiguration - Cannot find template.

I cloned repository from master branch today.
I got a lot of logs saying:

14:20:40.556 [main] WARN o.s.b.a.g.t.GroovyTemplateAutoConfiguration - Cannot find template location: classpath:/templates/ (please add some templates, check your Groovy configuration, or set spring.groovy.template.check-template-location=false)

during integration tests.

Cannot find Patron aggregate

Hello

On the very beginning many thanks for a great project!

I've just fetched repo into my machine and I have a problem to find Patron aggregate which is mention on project structure it's a little bit confusing. I think that more people would like to start learning domain from root aggregate and It would be also quite confusing for them.

Screenshot 2019-04-05 at 08 59 26

[Question] What factors affected your design decision?

Hi,

I only discovered DDD a few hours back and this repo was the perfect head start. Which otherwise would have only been possible after days of reading through multiple books. I have a quick question though:-

I was looking at this scenario and I can see this was modeled as two sub-models:-

  1. Closed-ended book holding and
  2. Open-ended book holding

It is, however, not very obvious to me how someone arrived at this specific model when there could clearly have been other possibilities like:-

  1. Hold for Regular patrons and
  2. Hold for researchers

How can one eliminate all the other possible models and what tools/methods did you (can I) use to arrive at this design?

Thanks in advance!
-Koba

Hack, placeholder or mistake?

Class HoldsToExpireSheet has annotation @EventListeneron method Stream<PatronEvent.BookHoldExpired> toStreamOfEvents(). What is this? Is it a hack or just placeholder for future improvements or maybe just a mistake?

Test MeteredDomainEventPublisherSpec is not running during build.

I cloned repository from master branch today.

When running mvn clean install I don't see every test:

[INFO] --- maven-failsafe-plugin:2.22.1:integration-test (default) @ library ---
[INFO]
[INFO] -------------------------------------------------------
[INFO] T E S T S
[INFO] -------------------------------------------------------
[INFO] Running io.pillopl.library.catalogue.CatalogueDatabaseIT
(...)
[INFO] Running io.pillopl.library.lending.book.infrastructure.BookDatabaseRepositoryIT
(...)
[INFO] Running io.pillopl.library.lending.book.infrastructure.DuplicateHoldFoundIT
(...)
[INFO] Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 7.468 s - in io.pillopl.library.lending.book.infrastructure.DuplicateHoldFoundIT
[INFO] Running io.pillopl.library.lending.book.infrastructure.FindAvailableBookInDatabaseIT
[INFO] Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.005 s - in io.pillopl.library.lending.book.infrastructure.FindAvailableBookInDatabaseIT
[INFO] Running io.pillopl.library.lending.book.infrastructure.FindBookOnHoldInDatabaseIT
[INFO] Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.001 s - in io.pillopl.library.lending.book.infrastructure.FindBookOnHoldInDatabaseIT
[INFO] Running io.pillopl.library.lending.book.infrastructure.OptimisticLockingBookAggregateIT
[INFO] Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.023 s - in io.pillopl.library.lending.book.infrastructure.OptimisticLockingBookAggregateIT
[INFO] Running io.pillopl.library.lending.dailysheet.infrastructure.FindingHoldsInDailySheetDatabaseIT
[INFO] Tests run: 6, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.15 s - in io.pillopl.library.lending.dailysheet.infrastructure.FindingHoldsInDailySheetDatabaseIT
[INFO] Running io.pillopl.library.lending.dailysheet.infrastructure.FindingOverdueCheckoutsInDailySheetDatabaseIT
[INFO] Tests run: 3, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.034 s - in io.pillopl.library.lending.dailysheet.infrastructure.FindingOverdueCheckoutsInDailySheetDatabaseIT
[INFO] Running io.pillopl.library.lending.eventspropagation.EventualConsistencyBetweenAggregatesAndReadModelsIT
[INFO] Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 2.697 s - in io.pillopl.library.lending.eventspropagation.EventualConsistencyBetweenAggregatesAndReadModelsIT
[INFO] Running io.pillopl.library.lending.eventspropagation.StrongConsistencyBetweenAggregatesAndReadModelsIT
[INFO] Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.021 s - in io.pillopl.library.lending.eventspropagation.StrongConsistencyBetweenAggregatesAndReadModelsIT
[INFO] Running io.pillopl.library.lending.patron.infrastructure.PatronDatabaseRepositoryIT
[INFO] Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.017 s - in io.pillopl.library.lending.patron.infrastructure.PatronDatabaseRepositoryIT
[INFO] Running io.pillopl.library.lending.patronprofile.infrastructure.FindingPatronProfileInDatabaseIT
[INFO] Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.036 s - in io.pillopl.library.lending.patronprofile.infrastructure.FindingPatronProfileInDatabaseIT
[INFO] Running io.pillopl.library.lending.patronprofile.web.PatronProfileControllerIT
(...)
[INFO] Tests run: 12, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 1.455 s - in io.pillopl.library.lending.patronprofile.web.PatronProfileControllerIT
(...)
[INFO] Tests run: 33, Failures: 0, Errors: 0, Skipped: 0

There's log about BookDatabaseRepositoryIT whis is annoted with @SpringBootTest
but there's no log about MeteredDomainEventPublisherSpec test which is weird.

Changing MeteredDomainEventPublisherSpec test to something like:

        then:
            countedEvents("domain_events", "name", "TestEvent") == 12121
        when:
            publisher.publish(new TestEvent())
        then:
            countedEvents("domain_events", "name", "TestEvent") == 13131

Clearly proves that this test is not runned.

JdbcConfiguration is deprecated

JdbcConfiguration is used in LendingDatabaseConfig that has been deprecated and recommended to use AbstractJdbcConfiguration

Architecture hexagonal ?

Hi,

Thank you for your example.

I have remarks about your implementation of architecture hexagonal pattern.
In architecture hexagonal we have port and adapter.
In my understanding,
Port is in domain package and adapter in infrastructure package.
(Golden rule : Infra can see domain.
Domain shouldn't see infra.)

However in your package catalogue you have CatalogueDatabase (adapter) and
CatalogueConfiguration (infra) and Catalogue (domain).

Do you really follow archi hexagonal pattern or am I wrong somewhere ?

Book that is currently not available - alternative

"When a patron tires to place on hold a book that is currently not available it should not be possible, thus resulting in book hold failed event, as it is depicted below"

The book view should not let the patrol to hold only available books ?
(grey out the others etc)

In this way will be impossible to to hold a book that is not available ....

Food for thoughts about extensions

I just wondered about the two things:

  1. No context map. IMHO this would be helpful to get an overview of the logical relationship between the bounded contexts https://github.com/ddd-crew/context-mapping
  2. Especially since ArchUnit is already used, it would be a reasonable improvement to incorporate https://github.com/xmolecules/jmolecules to further formally codify and validate the architectural concepts

PS: I deliberately misused the Issues feature since the Discussions feature seems to be disabled https://docs.github.com/en/discussions

Question about aggregates

According to this: Business logic one aggregate can never depend on a state from another aggregate: why in the patron model you inject the book model?

Not all tests are deterministic

I cloned repository from master branch today.
During mvn clean install I got once:

[INFO]
[INFO] Results:
[INFO]
[ERROR] Failures:
[ERROR] FindingPatronProfileInDatabaseIT.should create patron profile:65->thereIsOnlyOneHold:87 Condition not satisfied:

profile.holdsView.currentHolds.get(0) == new Hold(bookId, TOMORROW)
| | | | | | | |
| | | | | | | 2019-12-17T13:17:58.435995800Z
| | | | | | BookId(bookId=53fbcaff-78d1-4159-8516-4fa6f5b8b945)
| | | | | Hold(book=BookId(bookId=53fbcaff-78d1-4159-8516-4fa6f5b8b945), till=2019-12-17T13:17:58.435995800Z)
| | | | false
| | | Hold(book=BookId(bookId=53fbcaff-78d1-4159-8516-4fa6f5b8b945), till=2019-12-17T13:17:58.435996Z)
| | List(Hold(book=BookId(bookId=53fbcaff-78d1-4159-8516-4fa6f5b8b945), till=2019-12-17T13:17:58.435996Z))
| HoldsView(currentHolds=List(Hold(book=BookId(bookId=53fbcaff-78d1-4159-8516-4fa6f5b8b945), till=2019-12-17T13:17:58.435996Z)))
PatronProfile(holdsView=HoldsView(currentHolds=List(Hold(book=BookId(bookId=53fbcaff-78d1-4159-8516-4fa6f5b8b945), till=2019-12-17T13:17:58.435996Z))), currentCheckouts=CheckoutsView(currentCheckouts=List()))

[INFO]
[ERROR] Tests run: 33, Failures: 1, Errors: 0, Skipped: 0

2nd, 3rd, etc. time I runned tests their were fine.

Wrong domain abstractions

CatalogueDatabase and BookInstanceAddedToCatalogue are not objects in the domain. Why are these included in your model?

Remove findBy from save at BookRepository.save(Book book)

 @Override
    public void save(Book book) {
        findBy(book.bookId())
                .map(entity -> updateOptimistically(book))
                .onEmpty(() -> insertNew(book));
    }

this findBy is only there to have updateOrCreate behavior. But it makes optimistic locking doesn't work. in FindBy we fetch new Version

Aggregate domain model differences - Patron vs Book

Hello,
I've a question for concepts of implementing aggregates in your code.
In the readme there is a statement:

our domain model may become immutable and just return events as results of invocking a command

And it's nice, but on the other hand Book aggregate looks quite different. The Patron aggregate state is modeled in one class, and the previous conception is easy acceptable in this situation. But the Book aggregate, as I see accepts events, and returns new states (in the future I see here opportunity for event sourcing with small effort).
It's also OK, but why those two aggregates are modeled in different manners?
And what about compensation process for event BookDuplicateHoldFound - it should be handled by Patron aggregate, shouldn't it? So in this case the Patron aggregate also ought to contains method which accepts event and return new state.

Is the storage of the Book attributes redundant?

The values of some fields of BookDatabaseEntity are always the same, such as "available_at_branch" and "on_hold_at_branch" and "checked_out_at_branch", "on_hold_by_patron" and "checked_out_by_patron".

I can understand that these attributes have different meanings in the domain. but in terms of storage, is it feasible to map attributes with the same value to the same field, so that the number of fields can be reduced? For example, use "branch_id" and "patron_id".

class BookDatabaseEntity {
    UUID book_id;
    BookType book_type;
    BookState book_state;
    Instant on_hold_till;
    UUID branch_id;
    UUID patron_id;
    int version;
}

We can still judge the meaning of branch_id and patron_id through book_state.

Domain description adding Polish translation

Hey,

i spent some time trying to understood in details what the domain is about.
I suggest to add additionally polish translation.

This is my attempt to do so:

A public library allows patrons to place books on hold at its various library branches. 
Publiczna biblioteka pozwala patronom zarezerwować książki w różnych bibliotecznych oddziałach.

Available books can be placed on hold only by one patron at any given point in time. 
Dostępne książki mogą być zarezerwowane tylko przez jednego patrona w dowolnym czasie.

Books are either circulating or restricted, and can have retrieval or usage fees. 
Książki są ogólnodostępne, albo mają ograniczony dostęp i mogą mieć koszt za zwrot, czy użytkowanie.

A restricted book can only be held by a researcher patron.
Książki o ograniczonym dostępie mogą być zarezerwowane tylko przez patrona badacza. 

 A regular patron is limited to five holds at any given moment, while a researcher patron is allowed an unlimited number of holds.

 Zwykły patron jest ograniczony limitem ilości rezerwacji, wynoszącym pięć książek w dowolnym momencie, podczas gdy
 patron badacz może rezerwować ile chce.

 An open-ended book hold is active until the patron checks out the book, at which time it is completed.
 Rezerwacja książki na czas nieokreślony jest ważna dopóki patron jej nie odbierze (w chwili odebrania rezerwacja jest zakończona).

 A closed-ended book hold that is not completed within a fixed number of days after it was requested will expire.
 Rezerwacja książki na czas określony, która nie została zrealizowana po upływie ustalonego czasu traci ważność.

 This check is done at the beginning of a day by taking a look at daily sheet with expiring holds. 
 Sprawdzenie tego następuje na początku dnia - korzystając z arkusza zawierającego dane o przeterminowanych rezerwacjach.

 Only a researcher patron can request an open-ended hold duration. 
 Tylko patron badacz może zarezerwować książkę na czas nieokreślony.
 
 Any patron with more than two overdue checkouts at a library branch will get a rejection 
 if trying a hold at that same library branch. 
 Dowolny patron z więcej niż dwoma spóżnionymi odbiorami książek w danym oddziale bibliotecznym dostanie odmowe w przypadku, gdy
 będzie chciał zarezerwować książkę w tym samym oddziale bibliotecznym.

 A book can be checked out for up to 60 days.
 Książka może być wypożyczona na maksymalnie 60 dni.

 Check for overdue checkouts is done by taking a look at daily sheet with overdue checkouts. 
 Sprawdzenie przekroczonych czasowo zwrotów odbywa się poprzez korzystanie z dziennego arkusza z przekroczonymi czasowo zwrotami.

 Patron interacts with his/her current holds, checkouts, etc. by taking a look at patron profile. 
 Patron zarządza swoimi rezerwacjami, wypożyczeniami itp. - patrząc na swój profil patrona.

 Patron profile looks like a daily sheet, but the information there is limited to one patron and is not necessarily daily. 
 Profil patrona wygląda jak dzienny arkusz, ale informacje tam są ograniczone do jednego patrona i niekoniecznie dotyczą tylko jednego dnia.

 Currently a patron can see current holds (not canceled nor expired) and current checkouts (including overdue).
 Obecnie patron może zobaczyć aktualne rezerwacje (nie anulowane, ani nie te z utraconą ważnością)
 i aktualne wypożyczenia (włączając te przeterminowane). 

 Also, he/she is able to hold a book and cancel a hold.
 Dodatkowo on/ona mogą zarezerwować książkę i anulować rezerwację.

How actually a patron knows which books are there to lend? 
Skąd właściwie patron wie, które książki są do wypożyczenia?

Library has its catalogue of books where books are added together with their specific instances. 
Biblioteka ma swój katalog książek, gdzie książki są dodawane wraz z ich konkretnymi kopiami.

A specific book instance of a book can be added only if there is book with matching ISBN already in the catalogue. 
Konkretna kopia książki może zostać dodana tylko jeśli jest dostępna książka w katalogu z tym samym numerem ISBN.

Book must have non-empty title and price. 
Książka musi zawierać nie pusty tytuł i cene.

At the time of adding an instance we decide whether it will be Circulating or Restricted. 
Podczas dodawania kopii, decydujemy, czy będzie ona ogólnodostępna, czy o ograniczonym dostępie.

This enables us to have book with same ISBN as circulated and restricted at the same time 
(for instance, there is a book signed by the author that we want to keep as Restricted).
To pozwala nam mieć książkę z tym samym numerem ISBN jako zarazem ogólnodostępną jak i o ograniczonym dostępie (dla przykłada, jest książka podpisana przez autora, którą chcemy mieć z ograniczonym dostępem).

You can use it if you want to.

[Question] hold_database_entity and overdue_checkout_database_entity table not used?

create_patron_db.sql contains DDL to create patron_database_entity, hold_database_entity and overdue_checkout_database_entity. But it seems the later two are never used. io.pillopl.library.lending.patron.infrastructure.PatronsDatabaseRepository just query from patron_database_entity without joining from the other two tables.

interface PatronEntityRepository extends CrudRepository<PatronDatabaseEntity, Long> {

    @Query("SELECT p.* FROM patron_database_entity p where p.patron_id = :patronId")
    PatronDatabaseEntity findByPatronId(@Param("patronId") UUID patronId);

}

Create concept of PublishedEvent

So currently the all of the domain events have information about business facts.

If we want to publish them we need to add metadata like:

causationID,
corellationID,
uniqueMessageID
etc

so PublishedEvent becomes DomainEvent + metadata

Data consistency

Hi, I've been studying this project for a while. I really like what you've done here.

I would like to hear advice on how to solve the next issue. The consistency problem be replicated with this steps:

  1. Patron tries to hold a book with the controller.
  2. Patron Aggregate validates the command and then creates a BookPlacedOnHold event.
  3. BookPlacedOnHold arrives to the PatronsDatabaseRepository. The adds the new hold to the patron and then publishes the event.
  4. The event is listen in the Book's PatronEventsHandler
    Here comes the problem:
    If, for some reason, the database fails to persist the Book the database will be at an inconsistent state.
    The Patron will have a Hold but the book state will be Available.

Thanks in advance!

Question about packaging

Hey,

I am wondering is it better approach to use packaging by feature instead of packaging by layers using DDD.
In what direction your example packaging expand in the future?

Just by looking at the package structure:

└── library
    ├── catalogue
    ├── commons
    │   ├── aggregates
    │   ├── commands
    │   └── events
    │       └── publisher
    └── lending
        ├── book
        │   ├── application
        │   ├── infrastructure
        │   └── model
        ├── dailysheet
        │   ├── infrastructure
        │   └── model
        ├── librarybranch
        │   └── model
        ├── patron
        │   ├── application
        │   ├── infrastructure
        │   └── model
        └── patronprofile
            ├── infrastructure
            ├── model
            └── web

Does it start to use packaging by feature?

Update to spring boot 2.7.5 and Java 17

Hi, Thank you for sharing this project. This is a great example of DDD.

I tried to update the project to spring boot 2.7.5 as a transition to 3 but I ran into an issue with archunit.

io.pillopl.library.lending.architecture.NoSpringInDomainLogicTest is failing:

Architecture Violation [Priority: MEDIUM] - Rule 'no classes that reside in a package '..io.pillopl.library.lending..application..' should depend on classes that reside in a package 'org.springframework..'' was violated (7 times):
Method <io.pillopl.library.lending.book.application.CreateAvailableBookOnInstanceAddedEventHandler.handle(io.pillopl.library.catalogue.BookInstanceAddedToCatalogue)> is annotated with <org.springframework.context.event.EventListener> in (CreateAvailableBookOnInstanceAddedEventHandler.java:0)
Method <io.pillopl.library.lending.book.application.PatronEventsHandler.handle(io.pillopl.library.lending.patron.model.PatronEvent$BookCheckedOut)> is annotated with <org.springframework.context.event.EventListener> in (PatronEventsHandler.java:0)
Method <io.pillopl.library.lending.book.application.PatronEventsHandler.handle(io.pillopl.library.lending.patron.model.PatronEvent$BookHoldCanceled)> is annotated with <org.springframework.context.event.EventListener> in (PatronEventsHandler.java:0)
Method <io.pillopl.library.lending.book.application.PatronEventsHandler.handle(io.pillopl.library.lending.patron.model.PatronEvent$BookHoldExpired)> is annotated with <org.springframework.context.event.EventListener> in (PatronEventsHandler.java:0)
Method <io.pillopl.library.lending.book.application.PatronEventsHandler.handle(io.pillopl.library.lending.patron.model.PatronEvent$BookPlacedOnHold)> is annotated with <org.springframework.context.event.EventListener> in (PatronEventsHandler.java:0)
Method <io.pillopl.library.lending.book.application.PatronEventsHandler.handle(io.pillopl.library.lending.patron.model.PatronEvent$BookReturned)> is annotated with <org.springframework.context.event.EventListener> in (PatronEventsHandler.java:0)
Method <io.pillopl.library.lending.patron.application.hold.HandleDuplicateHold.handle(io.pillopl.library.lending.book.model.BookDuplicateHoldFound)> is annotated with <org.springframework.context.event.EventListener> in (HandleDuplicateHold.java:0)

How should I handle this?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.