Friday 28 December 2012

Enterprise Example Part 1

I have recently completed a cut down example of an entire enterprise, based on Event Driven Architecture:

Click here to get the code form GitHub. This blog post is based around commit 97d6ab2ab0.

To get this up and running, you should first have the NServiceBus examples downloaded and running succesfully. Then in download the code for this example, run the SQL Scripts in the root on your local databases, change all hibernate.cfg.xml files in the projects to point at your local SQL Server and run!

Overview


The enterprise consists of four systems:
  • Human Resources
  • Sales
  • Client Services
  • Finance
The term "Service" in this example relates to anything below the UI. The following rules are being followed for inter-service and ui-service communication:
  • UIs may only query external services using WCF.
  • UIs may send commands to services via NServiceBus messages. (This is currently synchronous but I will explore asynchronous communication is later posts.)
  • Services may only communicate with other services using events. (I may exploye asynchronous commands in later posts.)
The workflow for each system and how they interact are described below.


Sales


  • A sales consultant is presented with a list of leads. When a consultant returns from visiting a lead, they log this by clicking "Show Visits" and clicking "Log New".
  • The consultant fills in information about the visit.
  • When the consultant click "Create", a command is sent to the ‘Log Visit’ Message Handler in Sales, which adds a record about the visit.
  • If "Resulted in Deal" was not checked, the consultant is returned to the list of visits. If it was checked, the consultant is forwarded to the form to register a deal.
  • The consultant fills in this form and clicks "Create".
  • This sends a command to the "Register Deal" Message Handler in Sales, and also to "Initialize Client" Message Handler in Client Services.
  • The "Register Deal" handler adds a record about the deal, recording that consultant against it and calculating his or her commission.
  • The consultant is forwarded to the list of deals.
  • The consultant, Sales system and department are no longer concerned with this lead.
  • At any point, the system can receive an "Employee Left" event from Human Resources. In this situation, the relevant handler unassigns any leads assigned to that consultant.


Client Services


  • The "Initialize Client" command is received by the relevant Message Handler in the Client Services system.
  • This adds a record to the user’s list of clients to be activated, and adds an agreement with the start and end dates of the agreement, together with its value, as entered by the sales consultant.
  • The user clicks on "Activate" for the new client.
  • The user is presented with a form containing all information from the Sales system (name, address, phone number) which he or she must confirm. The user must also enter a reference for the client and a liaison whom they will deal with.
  • The user clicks "Activate".
  • A command is sent to the "Activate Client" Message Handler. This handler updates the information about the client and activates the current agreement. It also publishes an "Agreement Activated" event that the agreement has been activated.
  • The Client now moves from the list of ‘Clients Requiring Activation’ to the list of "Active Clients".
  • The user can then click on "Agreements" for any active clients to see the agreements (at this time it only show the current one).
  • The user can then click "Cancel" to signify that the client has cancelled the agreement. This sends a "Cancel Agreement" command. The handler for this marks the specified agreement as cancelled and raises an event to signal that this has happened.
  • At any point the system can receive a "Account Suspended" event from the Finance system. This signifies that the client’s account has been suspended (because they have fallen behind in their payments). The relevant handler picks this up and marks the corresponding agreement as suspended.
  • At any point, the system can receive an "Employee Left" event from Human Resources. In this situation, the relevant handler unassigns any clients assigned to that employee.


Finance


  • The "Agreement Activated" event is received by the relevant message handler in the Finance system. This opens a new Account and calculates all the monthly Installments that will need to be paid during the lifetime of the account.
  • The list of Accounts is displayed to the user. The user may click on "View Installments" to for any particular Account.
  • For each Installment, if the due date is not reached, it is marked as "Pending", if the due date has passed it is "Due", if it is ten days passed the due date it is marked as "Overdue". It the Account is closed it is marked as "NotRequired".
  • At any point, the user can click on "Mark As Paid" for an installment. This sends a command to the "Mark Installment As Paid" handler, which updates the domain accordingly. The Installment will then go to the "Paid" state.
  • Back in the list of Accounts, the user can click "Suspend" (if a client has fallen too far behind on their payments). This sends a command to the "Suspend Account" handler, which updates the domain and raises an "Account Suspended" event. (This is subscribed to by Client Services).
  • At any point, the system can receive and "Agreement Cancelled" event (raised by Client Services) and updates closes the corresponding Account.


Human Resources


  • Users of Human Resources can at any point mark an employee as left. This sends a "Mark Employee as Left" command to the Human Resources Message Handler, which updates the domain and raises the corresponding event (subsribed to by Sales and Client Services).


Shortcomings


It is important to not that many aspects of this code are not ideal. I am exploring EDA here, and do to limited time I have skimped on other areas. These areas include validation (both in the UI and the domain), user friendlines, and performance considerations. For example, currently Sales could fail if the user tries to register a Deal for a Lead that is unassigned. At some point it needs validation around this. It has not been done because it is not relevant to the concepts being explored here.

Error handling has not been implemented particularly well - I will be exploring this more in another post. This will probably involve returning ReturnCode.Error from MessageModule.HandleError() but I'm not sure yet.

Finally, The IOC implementation in my WCF services is a bit clumsy - I am still searching for a good solution to this.

In the event of an error, the system seems to retry forever. I'm not sure why it does this because I don't think this is default behviour in NServiceBus.


Upcoming Improvements


  • I would like to be able to book holidays for Employees in Human Resources. I would also like to be able to pre-book visits in the Sales system. These bookings should not be allowed to clash, so possibly some sort of central booking service is required.
  • When an employee is marked as left, as well as Leads being unassigned in Sales, and Clients being unassigned in Client Services, the manager of those departments should also be informed which Leads/Clients have been affected by email.
  • I would like to be able to issue an invoice from Finance. This would possibly be in the form of an email that would compose data form multiple services.
  • When a Deal is registered, the value is passed to Client Services and saved in the Agreement. Value has no real place in the Client Services domain – it is of no interest to them (under current specification). The only reason it is recorded is so that it can pass it to Finance when the agreement is activated, so Finance know how much to bill the client. There should be another way of getting this information to accounts – possibly using a Saga? Start and End dates of the Account could be passed through straight from the form rather than Client Services.
  • When a Client is initialised, no information is brought through (such as address lines, phone number, even name) except the Id. When the user activates the account, the UI queries Sales for the information so the user can confirm it, and it is then saved. It would be better if the information was passed straight into the initialised Account, but this information is not available on the register deal form. I will look into the possibility of using a Saga here to bring through this information.
  • In Client Services, Agreement has a possible status of Expired. This is not derived from the expiry date but it is a persistent value. The only way to ensure this is set correctly is to have a continuous polling service that would set it when the expiry date is reached. This could also be useful for raising an event for other domains.