PacktPublishing / Hands-On-Domain-Driven-Design-with-.NET-Core

Hands-On Domain-Driven Design with .NET Core, published by Packt
MIT License
636 stars 206 forks source link

How to work with sub-entities following the functional approach #27

Closed iappwebdev closed 3 years ago

iappwebdev commented 3 years ago

I finished the book and I am trying to accomplish the functional approach from the project Marketplace.PaidServices.Domain with separating aggregate state and its actions. How would you do this for ClassifiedAd in the project Marketplace.Ads.Domain? Because there are events that are passed to the child entity Picture when it's Added or Resized:

// picture
case V1.PictureAddedToAClassifiedAd e:
    picture = new Picture(Apply);
    ApplyToEntity(picture, e);
    _pictures.Add(picture);
    break;

case V1.ClassifiedAdPictureResized e:
    picture = FindPicture(new PictureId(e.PictureId));
    ApplyToEntity(picture, @event);
    break;

The examples OrderState and ClassifiedAdState don't have any entities and I can't figure out how to solve the problem with picture = new Picture(Apply). The reason is because previously the child entity recieved the Apply method from its AggregateRoot and with the functionnal approach, I can't manage to get it working.

Any idea how this approach can be done?

alexeyzimarev commented 3 years ago

If I get back to that project now, I would remove entities entirely. Having entities just adds unnecessary complexity. Accessing entities is still done from the aggregate root, so it's quite easy to move the code to the aggregate root code anyway.

I wrote about it in Eventuous docs.

Let me know if makes sense :)

iappwebdev commented 3 years ago

Thank you Alexey, also for the quick reply ;) Yes, that makes sense indeed. Previously I liked the way to give entities its own responsibility but at the end I think it is easier to handle that stuff in the aggregate root as it decreases complexity.

I went through the whole documentation of Eventuous and I have to admit I like it a lot. I didn't know about it and I'm wondering why it has not that much attention yet. It's a little (in terms of code) but good project that makes a lot of things easier to handle in the new DDD world. (By the way, you posted a localhost-link, the correct link is: Eventuous docs)

After reviewing the code for Aggregate and Aggregate State: I was just wondering how you validate the new state after applying an event. As I could not see a possibility to override Apply(), my workaround would be to call EnsureValidState() at the end of my When() method. But in this case, the call to EnsureValidState() is not guaranteed. In your book, you used the abstract method EnsureValidState() in the Apply() method. In Eventuous, this is not the case. Could you elaborate why you dropped this approach?

EDIT Maybe the reason is that in the moment an event has been applied, there should be no way to undo/drop it. In this case, all logic in EnsureValidState() should be distributed on the behaviour methods...?

alexeyzimarev commented 3 years ago

I don't think it makes sense to override When as it must work unconditionally (I believe I mentioned it in the book), as the logic for valid state check can change in time and applying it in the When function could break the aggregate rebuild.

Concerning the attention level to Eventuous, I only made it public a couple of weeks ago, so the current number of stars is actually quite high, considering the fact that I've done very little to promote it.

As you have seen, the AggregareState is now a record, so there will be a new version of the state after each Apply. Basically, you can just call EnsureValidState at the end of the aggregate method. You can also keep the original version of the state in a local variable, then check if the state transition is valid, by comparing the previous and the new state.

alexeyzimarev commented 3 years ago

The reason why I didn't put the EnsureValidState there as an abstract function is that it forces you to implement it even if you don't want to. There are numerous ways to do the state transition checks, and not always throwing an exception in EnsureValidState is the right way. For example, you might want to emit a failure event instead, which is then returned to the client (hence the Result record returns new events to the caller). Doing this would require either handling state transition checks individually, as they might produce different failure events, or using an EnsureValidState function, which returns a boolean, instead of throwing.

alexeyzimarev commented 3 years ago

That being said, nothing prevents me from making the Apply function virtual, so you can override it and do whatever you want after calling base.Apply

alexeyzimarev commented 3 years ago

So, I made it available, basically, both things.

Example usage: https://github.com/Eventuous/eventuous/blob/70717a805cc7baeccd52e2f37ae3da9169f220a2/test/Eventuous.Tests/Model/Booking.cs#L19-L29

iappwebdev commented 3 years ago

Hey Alexey, you're right, the When must not fail.

Regarding Eventuous: I read the blog post Say hello to Eventuous from November 4, 2020, so I thought that it had already been published that day.

Your explanations make sense, no need to have an abstract version of EnsureValidState. The new version on Apply is very helpful, it makes thinks a lot easier and more maintainable. By the way I don't think that returning the current state is practically useless, because it allow to be explicitly which state is used. Your example states this very well:

if (!previousState.IsFullyPaid() && currentState.IsFullyPaid())
    Apply(new BookingFullyPaid(State.Id));

I have a suggestion for Eventuous regarding the ApplicationService but I will open an issue in its own repo.

Thank you for the effort. I bought you some coffees ;)