php-ddd
php-ddd copied to clipboard
Finding aggregate (root) and naming (factory) methods
What really interests me is defining the actual aggregate root from the Order POV.
In my current company I have to processes:
- Customer talks to Employee. Employee logs in, creates an offer, sends it to Customer
- Customer receives Offer, agrees, informs Employee
- Employee logs in, enters the final order. Generates an Order Confirmation and sends it to the Customer
- Customer confirms order
- Employee logs in - an actual "Contract" is created. Work can begin.
Regarding the Offer and Order it is the Employee with the active part. In the red book there is this example "Table 1.5 Analyzing the best model for the business.": http://www.informit.com/articles/article.aspx?p=1944876&seqNum=3
Now I started thinking that it should be something like:
- Employee->sendOfferToCustomer
- Employee->sendOrderToCustomer
- Customer->confirmOffer
or at least:
- Customer->receiveOffer
- Customer->receiveOrder
- Customer->confirmOffer
instead of:
- Customer->requestOffer
- Customer->placeOrder
- Customer->confirmOffer
This really confused me! ;) My feeling as a developer tells me to use the last approach. What do you think @yvoyer ?
I created an example of how I would do your requirements. They are not working example, just code fragment from which you can get a basic idea.
Here are what classes or methods I extrapolated from your statements:
Customer talks to Employee. Employee logs in, creates an offer, sends it to Customer
- I created an
Employee->createOffer(): Offerthat is handled by a command handleCreateOfferHandlerwhich could be called from some controller. - The event listener
SendOfferToCustomer::onOfferWasCreated(OfferWasCreated $event)manage the sending of the offer to the customer.
Customer receives Offer, agrees, informs Employee
- The command handler
AgreeToOfferHandlercalls theCustomer->agreeToOffer(OfferId)which change to state of the offer withOffer->agree(). - An event
CustomerAgreedToOfferevent is triggered, and received by theOfferAgreedNotifierlistener. From there you can do whatever there is to do to notify the employee.
I did not completed all the requirements, but I hope this gives you a better idea.
Now I started thinking that it should be something like:
Employee->sendOfferToCustomer Employee->sendOrderToCustomer Customer->confirmOffer
or at least:
Customer->receiveOffer Customer->receiveOrder Customer->confirmOffer
IMO, The Customer should not know about the receiveOrder, receiveOffer, and the Employee should not know about the sendOfferToCustomer or sendOrderToCustomer since Customer and Employee seems to be 2 aggregates roots from 2 differents context. It is ok for them to receive Id, but the aggregate objects should probably not be passed to this context from any other places than the handlers.
But then again, I don't know all the requirements or needs, so it all depends on your domain.
Good luck, and hope it help.
Great feedback @yvoyer ... again.
So looking at Employee and Customer from a different Context really changes the responsibility of the actor on the Order Domain Model?
Indirectely the Customer is "placing an Order" when calling. But it is the Employee who adds it to the system.
Surprising and exciting at the same time.
BTW: Do you recommend using the Symfony Event Listeners or have you tried the @SimpleBus EventBus? https://github.com/SimpleBus/EventBus
P.S. on terminilogy:
- Isn't it Offer
Statusinstead ofState? - Isn't it better to use
submitOffer / placeOrderinstead ofcreateOffer / createOrdereven on the Employee?
Regarding: https://www.ego4u.com/en/business-english/communication/offer-order
BTW: Really like the $offer = self::PendingOffer($responsible); approach!
I have not tried SimpleBus yet, but it seems simple. I heard good review about it though.
I have used lite-cqrs for commands and EventDispatcher from symfony.
The only downside of the symfony dispatcher is having to extend the Event class. Try the lib that you find will help you accomplish your goal sooner.
About the terminology, I would probably go with terms like you said submitOffer and placeOrder as they are probably more meaningfull to the domain users. You should probably ask your domain expert to see which terminology is better for them.
BTW: Really like the $offer = self::PendingOffer($responsible); approach!
My pleasure, This is a good example of factory method. Since PHP do not enable multiple construct definitions, the static methods acts as constructs. I would also recommend to put your __construct private to force the dev (an you) to use the static methods instead of the construct. Makes the code more meaningfull.
For the State vs Status it a mather of choice on your part.
And again we agree @yvoyer ! I will try to adapt what we talked about to my current project. There will be feedback by the Customer and the Developers at the beginning of next month. I will post some approved code samples then! Thanks so far!
One question regarding your getOffer method:
https://gist.github.com/yvoyer/c2c016b7fe5288554e3f6c432acafd65#L124-131
Though it is the Customer that indirectly confirms an Offer it is still the Employee who uploads an Order Confirmation and by this marks it as confirmed.
So I guess I have to move this method to the Employee too.
If I moved the getOffer method there would the Employee have an $offers collection I would use like this?
class Employee
{
$offers = array(); // some extra lazy Doctrine ArrayCollection possibly
private function getOffer(OfferId $id)
{
return $this->offers[$id];
}
}
While it would make sense to add the same $offers collection on the Customer the getOffer method should be called on the Employee side, right @yvoyer ?
For me it still makes sense for the customer to have a getOrder (OrderId):Order. Since it is private, it do not bloth the domain, but it is still usefull to fetch 1 order. If you need it also in the employee class, it's okay, but I would keep it private as long as possible.
Try not to get the object B of another object A to perform an operation. Just tell object A to perform operation on B, that way A keeps control, and any validation constraint are encapsulated in A.
side note
Employee logs in - an actual "Contract" is created. Work can begin
From this sentence I would probably create a Employee::getApprovedContract (ContractId): Contract. When the Customer would confirm its Order, the system would automatically create the Contract and assign it to the employee and customer using events.
That brings me back to the beginning of this discussion.
As I mentioned it isn't the Customer in person placing the Order. He calles the Office and the Employee enters it into the system. Later when the Office receives the Order Confirmation it uploads it.
Maybe Office / Employee are just Roles inside the System. Maybe placing and Confirming the Order should ONLY happen in the Customer Domain Model.
There is no urgent need to find out which Employee created which Order. In the end the Employee is just a placedBy or confirmedBy link to the User behind Employee.
Is this still okay from a DDD and Context view?
If the customer never touches the system, I would definitely put the task to create the order on the employee. You would still need the customer ID to create the order (but not the whole customer).
Maybe instead of an Employee you need a new domain entity named SalesRepresentative or any other name meaningful for your domain expert. As an employee might be too generic, as it depends upon the bounded context. In the human ressource context, it might make sense to have an Employee. Where in a sale context, you might have the SaleRepresentative which is in charge of taking order from customer.
Then the order would be shipped for confirmation to the shipping context where the order would be prepared by a Shipper to the buyer (customer with address info).
Finally when the order is shipped it might be sent to the quality assurance context to be handled by the AfterSaleRepresentative to take any complaints.
Maybe what is making your domain ambiguous is that you might need to explode it in context and see which finner grained entity is missing. You probably will find with your domain expert that some other terms are missing.
Actually we do have something like a SalesRepresentative. The Employee belongs to the Office. Though Office simply extends Employee atm.
And the Office will have to confirm the Order after having placed it.
I'm wondering how the Office will receive the Offer to change it.
- Would a Service get the Customer and then get the Order from the repository and pass it to the Office?
- Should the Office get the Order directly from the Customer
return $orders[$orderId->id()]?
In both cases an $order has to be returned to be updated in the repository.
You see I still have problems with "ignoring the persistence". :/