DDD Practice Case: Introducing event-driven and middleware mechanisms to implement background management function I. INTRODUCTION
In the current e-commerce platform, the user after the order, then the store will be in the background to see the customer orders, and then the store can be the customer's orders to ship operations. At this point the customer will see that the store has shipped in their order status. As you can see from the business logic above, when the user finishes the order, the store or the administrator can track and manipulate the customer's order. Previous topic we have implemented the ability to create orders, then the next step is the implementation of the background management function. So in this topic, we will detail how to implement the background management function in the online bookstore case.
Second, the implementation of authority management in the background management
In the background management, the first thing that needs to be implemented is the rights management, because the product management and other operations, you must be different user specified different roles, and then assign different permissions for different roles. This will ensure that normal users cannot perform some background operations.
However, the empowerment of roles and permissions is generally handled by the system administrator. So at the beginning of the creation of an administrator user, you can then log in as the administrator's account to manage background operations, including adding roles, assigning roles to users, adding users, and so on.
This is a matter of authority management. How does the system manage for different users in a new way?
A realization of its rights management is actually as follows:
- Different roles can see different links, and only users with the specified permissions can see the actions of their corresponding permissions. If only the administrator can add users and give permissions to users, and sellers can only deal with consumer orders and add products to their own stores and other operations.
As can be seen from the above description, the implementation of Rights management mainly consists of two parts:
- Specify a different link display for different users. As administrator can see all the links in the background management: including role management, product management, user management, order management, commodity classification management, and sellers can only see order management, commodity management and product category management. The implementation is to specify different permissions for the build of these links, only the user who has reached the permission to generate the link
- Now that you want to assign different permissions to different users, you first need to get the permissions of the user and then dynamically generate the corresponding links based on the user's permissions.
With the above ideas, let's join in the online bookstore case to add the function of Rights management:
First, I add a link to the specified permission on the Layout.cshtml page, as shown in the following code:
<table width= "996" border= "0" cellspacing= "0" cellpadding= "0" align= "center" > <tr> <TD He ight= "607" valign= "top" > <table width= "996" border= "0" cellspacing= "0" cellpadding= "0" > <tr> <td width= "height=" class= "logo" ></td> <TD width= "480" class= "menu" > <ul class= "Sf-menu" > <li> @Html. ActionLink ("Home", "index", "Home") </li> @if (User.Identity.IsAuthen ticated) {<li> @Html. ActionLink ("My", "Manage", "Accou NT ") <ul> <li> @Html. ActionLink (" Orders ", "Orders", "Home") </li> <li> @Html. ActionLink ("Accounts", "Manage", "account") </li> <li> @Html. ActionLink ("Shopping cart", "ShoppingCart", "Home") </li> </ul> </li>} @if (User.Identity.IsAuthenticated) {<li> @Html. Actionlinkwithpermission ("admin", "Administration", "admin", Permissionkeys.administrators | Permissionkeys.buyers | Permissionkeys.salesreps) <ul> < ;li> @Html. Actionlinkwithpermission ("Sales order Management", "Orders", "Admin", Permissionkeys.administrators | permissionkeys.salesreps) </li> <li> @Html. Actionlinkwithpermission (" Commodity classification Management "," Categories "," Admin ", Permissionkeys.administrators | permissionkeys.buyers) </li> <li> @Html. Actionlinkwithpermission ("Product letter Management "," Products "," Admin ", Permissionkeys.administrators | permissionkeys.buyers) </li> <li> @Html. Actionlinkwithpermission ("User account "UserAccounts", "admin", permissionkeys.administrators) </li> <li > @Html. actionlinkwithpermission ("UserRole management "," Roles "," Admin ", permissionkeys.administrators) </li> </ul> </li>}<li> @Html. ActionLink ("About", "about", "Home") <ul> <li> @Html. ActionLink ("Online Store Project", "about", "Home") </li> <li> @Html. ActionLink ("Contacts", "Contact", "Home") </li> </ul> </li> </ul> </td> <TD width= "216" class= "menu" > @{html.renderaction ("_loginpartial", "Layout"); } </td> </tr> </table> <table Width= "100%" border= "0" cellspacing= "0" cellpadding= "0" > <tr> <td w Idth= "100%" height= "10px"/> </tr> </table> <table Widt H= "996" border= "0" cellspacing= "0" cellpadding= "0" > <tr> <td> </td> </tr> </table> <table width= "996" border= "0" cellspacing= "0" cellpadding= "0" > <tr align= "left" valign= "top" > <td width= "202" height= "334" > @{html.renderaction ("Categoriespartial", "Layout");} </td> <td width= "> </td> <td width=" 774 " ; <table width= "774" border= "0" cellspacing= "0" cellpadding= "0" > <tr> @ (MvcSiteMap.Instance.Navigator ()) </tr> <tr> <td> @RenderBody () & lt;/td> </tr> </table> < ;/td> </tr> </table> <table width= "996" border= "0" cell spacing= "0" cellpadding= "0" > <tr> <td> </td> </tr> <tr> <td height= "" "> <table width=" 996 "border=" 0 "cell spacing= "0" cellpadding= "0" align= "center" > <tr> <TD width= "329" height= "" "align=" right "></td> <td width=" >& " ;nbsp;</td> <TD width= "653" ><span class= "Style7" > @Html. ActionLink ("Home", "Index", "Home") & nbsp; | @Html. ActionLink ("All Categories", "category", "Home", NULL, NULL) | & nbsp @Html. ActionLink ("My Accounts", "account", "Accounting") | @Html. ActionLink ("Contact Us", "Contacts", "Home") | @Html. ActionLink ("About this site", " About "," Home ") </span><br> All rights reserved © 2014-2015, Online Store, All rights reserved. </td> </tr> </table> &L t;/td> </tr> </table> </td> </tr> </t Able>
The red bold part above is to set different permissions for different roles. Where Actionlinkwithpermission is an extension method, the specific implementation is to obtain the role of the login user, and then the user's role and the current need to compare the permissions, if the same, The Htmlhelper.generatelink method is used to generate the corresponding link. The implementation code for this method is as follows:
public static mvchtmlstring actionlinkwithpermission (This htmlhelper helper, string linkText, String action, string contr Oller, Permissionkeys required) {if (helper = = NULL | | Helper. ViewContext = = NULL | | Helper. Viewcontext.requestcontext = = NULL | | Helper. ViewContext.RequestContext.HttpContext = = NULL | | Helper. ViewContext.RequestContext.HttpContext.User = = NULL | | Helper. ViewContext.RequestContext.HttpContext.User.Identity = = null) return mvchtmlstring.empty; using (var proxy = new Userserviceclient ()) {var role = proxy. Getrolebyusername (helper. ViewContext.RequestContext.HttpContext.User.Identity.Name); if (role = = null) return mvchtmlstring.empty; var keyName = role. Name; var Permissionkey = (Permissionkeys) enum.parse (typeof (Permissionkeys), keyName); Through userThe role and corresponding permissions are performed with the action//with the result equal to the user role, which means that the user role is the same as the required permissions, then create the corresponding permission link return (Permissionkey & Requir ed) = = Permissionkey? Mvchtmlstring.create (Htmlhelper.generatelink (helper). Viewcontext.requestcontext, Helper. RouteCollection, LinkText, NULL, action, CONTROLLER, NULL, NULL)): Mvchtmlstring.empty; } }
Through the above code, we have completed the implementation of the Rights management.
Third, the implementation of the management of goods in the background
If you are the administrator, then you can go to the background page to face goods, users, orders and other management. In the above we have completed the implementation of Rights management. Next, we can login with an administrator account, you can see the administrator's corresponding permissions. Here I directly add an administrator account in the database, the account information is admin, password is also admin. Below I will see the interface as shown in this account:
As can be seen, background management includes sales order management, product category management, commodity information management and so on. These are some similar implementations, all of which are the implementation of the function of adding, deleting and modifying. Here is the product information management as an example to introduce. After clicking on the product information management, you will be able to see a list of all items that can be added, modified and deleted in the product. Its implementation is mainly through the application of services to call warehousing to achieve the persistence of commodity information, the following specific introduction of the implementation of the product add function. Because the addition of the product needs to first add the uploaded image to the images folder on the server, and then through the controller to call the Productservice Createproducts method to save the product to the database.
The first is the implementation of the image upload function, the implementation code is as follows:
[HandleError] public class Admincontroller:controllerbase {#region Common Utility Actions//Save picture to server-specified directory [nonaction] private void SaveFile (HttpPostedFileBase postedFile, String FilePath, String savename) {String phypath = Request.mappath ("~" + FilePath); if (! Directory.Exists (Phypath)) {directory.createdirectory (Phypath); } try {Postedfile.saveas (Phypath + savename); } catch (Exception e) {throw new ApplicationException (e.message); }}//The implementation of the image upload function [httppost] public actionresult Upload (HttpPostedFileBase fileData, string fol Der) {var result = string. Empty; if (fileData! = null) {string ext = path.getextension (filedata.filename); result = Guid.NewGuid () + ext; SaveFile (filEData, Url.content ("~/images/products/"), result); } return Content (result); }}
Once the image has been uploaded successfully, click the Save button to persist the item to the database. The implementation of the logic is mainly to call the implementation of commodity warehousing class to complete the product additions. The main implementation code is as follows:
[HandleError] public class Admincontroller:controllerbase {[HttpPost] [authorize] public A Ctionresult addproduct (productdto product) {using (var proxy = new Productserviceclient ()) { if (string. IsNullOrEmpty (product. IMAGEURL)) {var fileName = Guid.NewGuid () + ". png"; System.IO.File.Copy (Server.MapPath ("~/images/products/productimage.png"), Server.MapPath (string. Format ("~/images/products/{0}", FileName)); Product. IMAGEURL = FileName; } var addedproducts = proxy. Createproducts (new List<productdto> {product}. ToArray ()); if (product. Category! = null && product. Category.id! = Guid.Empty.ToString ()) proxy. Categorizeproduct (New Guid (addedproducts[0). ID), the new Guid (product. Category.id)); Return redirecttosuccess ("Add Product info Success! "," Products"," Admin "); }}}//Implementation of the commodity service public class Productserviceimp:applicationservice, Iproductservice {publi C list<productdto> createproducts (list<productdto> productsdtos) {return PERFORMCREATEOBJEC Ts<list<productdto>, Productdto, product> (Productsdtos, _productrepository); } }
To this, we have completed the implementation of the product add function, let us see how the product added to the specific effect. Add Product page:
Click the Save Changes button, then add the product, add the successful interface effect:
Iv. implementation of delivery operations and confirmation of receipts in the background management
When a consumer creates an order, the seller or administrator can then ship the order through the Order Management page. To inform the purchaser that the product has been shipped. In the current e-commerce site, in addition to the status of the update order, will also send an email or text message to inform the purchaser. In order to ensure that both operations are completed at the same time, you need to commit both of them in the same transaction.
In order to make the system more scalable, we use Message Queuing and event-driven methods to complete the shipment operation. Before we look at the specific implementation code, we will analyze the implementation of the idea:
- when the seller or administrator clicks the Ship button on the Order Management page, the status of the order is updated, from the paid status to the shipped status. Of course, you can do this in a traditional way, that is, call the order warehousing to update the status of the corresponding order. However, this implementation means that the mail-sending operation may be nested within the Application service layer. Such a design is obviously not suitable for expansion. Therefore, this approach is improved by using event-driven and Message Queuing methods.
- First, when the merchant clicks on the shipping action, a shipping event is generated, and
- is then processed by the domain event handler that is registered to handle the domain, and the processing logic is primarily the status of the update order and the time of the update;
- then publish the event to even A queue is saved in the Tbus,eventbus to hold events, and the implementation of the publish operation is to insert a pending event into the queue, and
- finally queues the events in the queue in the commit method in Eventbus. An event aggregation class is obtained to obtain the corresponding event handler to process the out-of-queue events. The
- event aggregator injects (applies) the event's processor through unity. Define _eventhandlers in the Eventaggregator class to hold all (applied) events to the processor, and in Eventaggregator's constructor, by calling its Register method to add the corresponding event handler to _ The Eventhandlers dictionary. Then, in the Commit method in Eventbus, by locating the handle method in the Eventaggregator to trigger the event handler to handle the corresponding event, an email notification is sent. Here the event aggregator functions as a mapping, mapping application events to the corresponding event handlers to handle.
Through the above analysis can be found that the delivery operations and receiving operations are related to 2 types of events, one is the domain event, the other is in the application event, the domain event processing by the domain event handler, and the application event processing can not be defined in the domain layer, so we have a new Application event processing layer, Called OnlineStore.Events.Handlers, has created a new layer of support for Eventbus, called Onlinestore.events. After the above analysis, to achieve delivery operations and receiving operations is not a little clear? If not, it does not matter, we can combine the following specific implementation code to understand the above analysis of the idea. Because the fulfillment of the receipt operation and the shipping operation is very similar, only the main code of the delivery operation implementation is posted here for demonstration.
The first is the implementation of the Dispatchorder operation in Admincontroller:
Public ActionResult Dispatchorder (string id) { using (var proxy = new Orderserviceclient ()) { Proxy. Dispatch (new Guid (ID)); Return Redirecttosuccess (String. Format ("Order {0} has been successfully shipped! ", ID. ToUpper ()), "Orders", "Admin"); } }
Next is the implementation of the dispatch method in OrderService:
public void Dispatch (Guid orderId) { using (var transactionScope = new TransactionScope ()) { var order = _orderrepository.getbykey (orderId); Order. Dispatch (); _orderrepository.update (order); Repositorytcontext.commit (); _eventbus.commit (); Transactionscope.complete (); } }
The following is the implementation of the dispatch method in the Order entity class:
<summary>/// handling shipments. //</summary> public void Dispatch () { //processing domain event domainevent.handle< Orderdispatchedevent> (New Orderdispatchedevent (this) {dispatcheddate = DateTime.Now, OrderId = this. Id, useremailaddress = this. User.email}); }
The next step is the implementation of the handle method in the domain event, which is to obtain all registered domain event handlers and then invoke them separately by the event handler. The specific implementation code is as follows:
public static void Handle<tdomainevent> (Tdomainevent domainevent) where Tdomainevent:class, idomainevent { //find the corresponding event handler to process the event var handlers = servicelocator.instance.resolveall<idomaineventhandler< Tdomainevent>> (); foreach (var handler in handlers) { if (handler. GetType (). IsDefined (typeof (Handlesasynchronouslyattribute), false)) Task.Factory.StartNew (() = handler. Handle (domainevent)); else handler. Handle (domainevent); } }
The implementation of the handle method in the corresponding Orderdispatchedeventhandler class is as follows:
Shipping Event Processor Public class orderdispatchedeventhandler:idomaineventhandler<orderdispatchedevent> { private readonly Ieventbus _bus; Public Orderdispatchedeventhandler (Ieventbus bus) { _bus = bus; } public void Handle (orderdispatchedevent @event) { //Get Event Source Object var order = @event. Source as Order; Updates the properties of the event source object if (order = = null) return; Order. Dispatcheddate = @event. Dispatcheddate; Order. Status = orderstatus.dispatched; Here, the domain event is considered a message that is pushed into the eventbus for further processing. _bus. Publish<orderdispatchedevent> (@event); } }
As you can see from the above code, the domain event handler simply updates the status of the order status to dispatched and updates the order delivery time, after which the event continues to be posted to Eventbus for further processing. The specific implementation code for the Eventbus class is as follows:
Domain event handlers Simply update the state of the event object//follow-up event processing to eventbus for processing///The Eventbus main task in this case is to send an email notification,//to handle the application event in Eventbus, while the domain event handler General processing domain event public class Eventbus:disposableobject, Ieventbus {public Eventbus (Ieventaggregator Aggregato r) {this._aggregator = aggregator; Obtain the handle method in the Eventaggregator _handlemethod = (from M in aggregator. GetType (). GetMethods () Let parameters = M.getparameters () Let MethodName = M . Name where methodName = = "Handle" && Parameters! = NULL &A mp;& parameters. Length = = 1 Select m). First (); } public void publish<tmessage> (Tmessage message) where Tmessage:class, IEvent {_messagequeue.value.enqueue (message); _committed. Value = false; }//TriggerApplying event handlers to handle events public void Commit () {while (_messagequeue.value.count > 0) { var evnt = _messagequeue.value.dequeue (); var evnttype = evnt. GetType (); var method = _handlemethod.makegenericmethod (Evnttype); Invokes the application event handler to process the application event. Invoke (_aggregator, new object[] {evnt}); } _committed. Value = true; } }
The implementation of its Eventaggregator class is as follows:
View Code
As for the confirmation of the delivery operation is similar, you can refer to the GitHub source code for implementation. Here, our product delivery and confirmation of the receipt of the function of the completion of the implementation. At this point, our solution has been adjusted to:
After this topic, our online bookstore case business functions are almost finished, some of the features added later are additional features, such as distributed cache support, distributed Message Queuing support, and support for cutting-side programming and other functions. Now that the business functions are almost complete, let's take a look at the implementation of the delivery operation.
First, the Sales Order Management home page, where you can see all the user's order status. The specific effect is as follows:
Click on the Shipping button to complete the product shipment operation, the user who created the order will receive an e-mail notification, the implementation effect is as follows:
Its confirmation of the delivery operation to achieve the effect of the same as the effect of shipping operations, there is no one, you can go to github download the source code to run the view.
V. Summary
Here, the content of the presentation of the topic is over. This topic mainly introduces the functions of authority management in background management, commodity management, category management, role management, User role management and order management. As mentioned above, here, the online bookstore of the DDD case some of the business functions are almost realized, the next need to improve the function is mainly some additional features, these functions are mainly to improve the scalability and scalability of the site. These mainly include cache support, distributed Message Queuing support, and AOP support. In the next topic, the support for distributed cache and distributed Message Queuing will be covered, so please keep your eye on it.
All source code downloads for this topic: https://github.com/lizhi5753186/OnlineStore_Second/
Classification:. NET domain Driven Design series Tags: DDD, Event, Eventbus, Dispatchorder
DDD Practice Case: Introducing event-driven and middleware mechanisms to implement background management functions