Open webdevilopers opened 8 years ago
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(): Offer
that is handled by a command handleCreateOfferHandler
which 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
AgreeToOfferHandler
calls theCustomer->agreeToOffer(OfferId)
which change to state of the offer withOffer->agree()
.- An event
CustomerAgreedToOffer
event is triggered, and received by theOfferAgreedNotifier
listener. 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:
Status
instead of State
?submitOffer / placeOrder
instead of createOffer / createOrder
even 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.
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". :/
What really interests me is defining the actual aggregate root from the Order POV.
In my current company I have to processes:
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:
or at least:
instead of:
This really confused me! ;) My feeling as a developer tells me to use the last approach. What do you think @yvoyer ?