. NET application architecture design-anti-corrosion layer function and design of the user end
Reading directory:
1. background 2. display-end architecture corruption under the SOA architecture 3. effective use of anti-corrosion layer to isolate fragment services, resulting in display end logic decay 4. strip the technical components of service calls to make them dependent on interfaces. 5. change the DTO and ViewModel of the Service to the anti-corrosion layer. 1. convert the logical process and write it directly in the method of anti-corrosion layer. 5. 2. the conversion logic is object-oriented, And the encapsulation and reuse structure is established to prevent further corruption. 6. anti-corrosion layer two dependent inversion Design Method 6. 1. event-driven (anti-corrosion layer listening displays logical events) 6. 2. dependency Injection Interface 7. conclusion 1. background
With the transformation of enterprise application architectures towards SOA, the goal is to divide a large business system by business, regardless of the company's management and product development, these processes are correct. SOA does provide a solution to the rapid expansion of large enterprise application systems.
But what we want to talk about in this article is that we all turn to the backend, that is, the server, and focus our energy and time on the architecture design of the backend service, the architecture design of the display end is gradually ignored. However, the logic of the display end is becoming more and more complex. In fact, the light and thin architecture of the display end has emerged the danger that it is difficult to cope with the rapid expansion of backend service interfaces. service interfaces are increasing exponentially, basically, each new business requirement provides a new interface, which is no problem. According to the service design principle, the service interface should have a clear role, rather than considering the interface design according to the code thinking.
However, the resulting problem is whether the structure of the display end combined with these interfaces is consistent with this change, and whether this change is made to make the display end logic complex.
Based on my own experience, I found that the architecture design of the display end is not valued. The importance here is not the attention of the boss, but not the attention of our developers. Of course, the time issue is ruled out here. I have observed many user interface project architectures, simple structures, no encapsulation, no reuse, and no design principles. This will make it difficult for these codes to be impacted by the service interface with the rapid promotion of the business. The biggest problem here is whether we have the consciousness of rapid reconstruction as a programmer, I like the professional quality of this programmer. It allows us to quickly and flexibly keep up with the changes in the project structure brought about by business development.
Iterative reconstruction has a subtle effect on the project. It cannot be too early or too late to be reconstructed, and it must be reconstructed as needed. My experience with refactoring is that it is a reconstruction signal when you are poorly written about new features, and it should be the best reconstruction time. Refactoring is not a special preparation time, But interspersed in the process of writing code. It is part of your code. So I think this is why TDD is accepted.
2. Corruption of the display end architecture under the SOA Architecture
I personally think there are two problems with the architecture corruption of the display end. First, the structure of the display end can work well in the traditional system architecture, but the overall architecture has changed, therefore, adjustments must be made in a timely manner. Second, the display end architecture cannot be reconstructed in a timely manner, the display end structure cannot be further separated, and the display logic can be tested independently.
In this way, with the increasing number of SOA interfaces, the display end directly embeds the methods for calling services into the display logic, such as ASP. NET Mvc, ASP. NET Webapi controller, including DTO conversion between two layers.
According to the context Design Method of DDD, you can also create a display-Oriented Domain Model on the User display end. This model is mainly used for pre-processing after the domain is about to reach the server end. After all, a domain entity has multiple responsibilities. Building a lightweight domain model on the display end will be of great benefit to restructuring the display logic, the premise is that you have complicated domain logic. (My previous company (a well-known e-commerce platform in the United States), whose display end has complicated domain logic, it is surprising that a display end is complex, if the domain model display context can be introduced on this basis, it will be very helpful for complicated logic processing. Of course, this is just an unverified guess, for your reference only .)
If you are interested in processing the display-side domain model, refer to the two articles I have written on this topic:
. NET application architecture design-query-Oriented Domain-driven design practices (adjust the traditional three-tier architecture, and add maintenance-oriented business switches)
. NET application architecture design-query-oriented parametric Query Design (decomposition of business points and separate configuration of their respective data query contracts)
The original clean display logic has many unrelated service call details, and there are many conversion logic and judgment logic, which did not belong to this place, putting them in the right place is helpful for restructuring and reusing the display logic.
If you do not remove it from the display logic, as the service interface continues to increase and expand, it will directly cause you to modify the display logic code, if your display logic code is the logic shared by MVC and Webapi, the situation is more complicated. The display logic will be occupied by the conversion between ViewModel and Service Dto, it's hard for you to find valuable logic.
3. Effective use of anti-corrosion layer to isolate fragment services, resulting in display end logic decay
The solution to these problems is to introduce the anti-corrosion layer. Although the anti-corrosion layer was originally designed to solve the transformation between domain models during system integration, I think there are many similarities between the current system architecture and integration, we can use these good design methods to solve similar problems.
After the anti-corrosion layer is introduced, all the code that should not have appeared in the display logic is moved to the anti-corrosion layer, and the OO mechanism is established in the anti-corrosion layer so that these OO objects can be used together with the display logic.
Figure 1:
The user Layer is divided into three sub-layers: UiLayer, Show Logic Layer, and Anticorrosive Layer. The last Layer is the service interface group. All service interface calls must go through the anti-corrosion Layer.
We need to migrate the service call in the Show Logic Layer and the type conversion code to the antimo-soive Layer. Here, the conversion Logic can be objectized or not. For details, see whether the project is required. If the business is indeed complicated, we need to visualize it for encapsulation and reuse.
4. Remove the technical components called by the Service to make it dependent on the Interface
The first thing to do is to re-construct the service objects in the logic code as interface-oriented, and then inject dynamic dependencies into the logic type. In ASP. in NETWEBAPI, we basically write the display logic here. I will also use this method to demonstrate the examples in this chapter, however, if your MVC project and WEBAPI project share the display Logic, you need to propose it to form an independent project (Show Logic Layer ).
using OrderManager.Port.Models;using System.Collections.Generic;using System.Web.Http; namespace OrderManager.Port.Controllers{ public class OrderController : ApiController { [HttpGet] public OrderViewModel GetOrderById(long oId) { OrderService.Contract.OrderServiceClient client = new OrderService.Contract.OrderServiceClient(); var order = client.GetOrderByOid(oId); if (order == null) return null; return AutoMapper.Mapper.DynamicMap
(order); } }}
This is a simple code for calling the Order Service. First, you need to instantiate a client proxy contained in the service contract, and then call the remote service method GetOrderByOid (long oId) through the proxy ). Execute a simple judgment and output OrderViewModel.
If all the logic is so simple, I don't need any anti-corrosion layer. Such type of display code is extremely simple, the purpose here is not to show how complicated code is to write, but to rebuild the layer interface of the Code called by the service call and inject it into the OrderController instance. The purpose is to perform unit tests on the Controller in subsequent iteration refactoring. This may be a bit of a hassle, but it is still required for long-term benefit.
using OrderManager.Port.Component;using OrderManager.Port.Models;using System.Collections.Generic;using System.Web.Http; namespace OrderManager.Port.Controllers{ public class OrderController : ApiController { private readonly IOrderServiceClient orderServiceClient; public OrderController(IOrderServiceClient orderServiceClient) { this.orderServiceClient = orderServiceClient; } [HttpGet] public OrderViewModel GetOrderById(long oId) { var order = orderServiceClient.GetOrderByOid(oId); if (order == null) return null; return AutoMapper.Mapper.DynamicMap
(order); } }}
To dynamically inject data into the Controller at runtime, you need to do some basic work to extend the initialization code of the MVC controller. In this way, we can perform a complete unit test on OrderController.
As I said just now, if the display logic is as simple as this, everything will be fine. The real display logic is very complicated and changeable, not all types can be converted using the Automapper dynamic ing tool. Some types can be converted using logic. The GetOrderById (long oId) method is used to demonstrate the refactoring service call component.
In most cases, we need to combine multiple service calls and combine multiple results to return them to the front-end, here, the Items attribute type OrderItem type in the OrderViewModel object contains a Product type attribute. Under normal circumstances, we only need to obtain the order entry, however, in some cases, the specific product information in the entries must be returned to the front-end for partial information display.
using System.Collections.Generic; namespace OrderManager.Port.Models{ public class OrderViewModel { public long OId { get; set; } public string OName { get; set; } public string Address { get; set; } public List
Items { get; set; } }}
The Items attribute in OrderViewModel is a List Set. Let's look at the OrderItem attribute again.
using System.Collections.Generic; namespace OrderManager.Port.Models{ public class OrderItem { public long OitemId { get; set; } public long Pid { get; set; } public float Price { get; set; } public int Number { get; set; } public Product Product { get; set; } }}
It contains a Product instance, which sometimes needs to be assigned a value.
namespace OrderManager.Port.Models{ public class Product { public long Pid { get; set; } public string PName { get; set; } public long PGroup { get; set; } public string Production { get; set; } }}
Some information in the product type is mainly used to display order entries in a more humane way. You only give a product ID and cannot let the user know which specific product it is.
Let's take a look at an example of rapid code expansion due to business changes. In this example, we need to obtain the complete Product information based on the Pid in OrderItem.
Using OrderManager. port. component; using OrderManager. port. models; using System. collections. generic; using System. web. http; using System. linq; namespace OrderManager. port. controllers {public class OrderController: ApiController {private readonly IOrderServiceClient orderServiceClient; private readonly IProductServiceClient productServiceClient; public OrderController (IOrderServiceClient orderServiceC Lient, IProductServiceClient productServiceClient) {this. orderServiceClient = orderServiceClient; this. productServiceClient = productServiceClient;} [HttpGet] public OrderViewModel GetOrderById (long oId) {var order = orderServiceClient. getOrderByOid (oId); if (order = null & order. items! = Null & order. items. count> 0) return null; var result = new OrderViewModel () {OId = order. OId, Address = order. address, OName = order. OName, Items = new System. collections. generic. list
()}; If (order. items. count = 1) {var product = productServiceClient. getProductByPid (order. items [0]. pid); // call a single interface for obtaining products if (product! = Null) {result. Items. Add (ConvertOrderItem (order. Items [0], product) ;}} else {List
Pids = (from item in order. Items select item. Pid). ToList (); var products = productServiceClient. GetProductsByIds (pids); // call the if (products! = Null) {result. items = ConvertOrderItems (products, order. items); // batch conversion of OrderItem type} return result;} private static OrderItem ConvertOrderItem (OrderService. orderItem orderItem, ProductService. contract. product product) {if (product = null) return null; return new OrderItem () {Number = orderItem. number, OitemId = orderItem. oitemId, Pid = orderItem. pid, Price = orderItem. price, Product = new Product () {Pid = product. pid, PName = product. PName, PGroup = product. PGroup, Production = product. production }};} private static List
ConvertOrderItems (List
Products, List
OrderItems) {var result = new List
(); OrderItems. forEach (item => {var orderItem = ConvertOrderItem (item, products. where (p => p. pid = item. pid ). firstOrDefault (); if (orderItem! = Null) result. Add (orderItem) ;}; return result ;}}}
My first feeling is that the display logic is basically a type conversion code, and I didn't add any display logic here. In this case, the Code expands rapidly, it is conceivable that if we add the display logic to these codes, it is basically difficult for us to maintain the display logic later, and these display logics are the real responsibilities of this class.
The resulting problem is that the important logic is drowned in the conversion code, so we urgently need a location that can accommodate the conversion code, that is, the anti-corrosion layer, in the anti-corrosion layer, we specifically deal with these conversion logics. Of course, my example here is relatively simple and only contains queries. The real anti-corrosion layer is very complicated, it processes nothing less than other logic processes. Here we are only converting some DTO objects instead of complex DomainModel objects.
5. Place the conversion between the DTO and ViewModel of the Service to the anti-corrosion layer.
We need an anti-corrosion layer to process the conversion code, including the call logic of the backend service. It will be helpful for us to rebuild the code after it is moved into the anti-corrosion object.
Namespace OrderManager. Anticorrsive {using OrderManager. Port. Component; using OrderManager. Port. Models; using System. Collections. Generic; using System. Linq ;////// OrderViewModel anti-corrosion object ///Public class OrderAnticorrsive: AnticorrsiveBase
, IOrderAnticorrsive {private readonly IOrderServiceClient orderServiceClient; private readonly IProductServiceClient productServiceClient; public OrderAnticorrsive (IOrderServiceClient orderServiceClient, specified productServiceClient) {this. orderServiceClient = orderServiceClient; this. productServiceClient = productServiceClient;} public OrderViewModel GetOrderViewModel (long oId ){ Var order = orderServiceClient. GetOrderByOid (oId); if (order = null & order. Items! = Null & order. items. count> 0) return null; var result = new OrderViewModel () {OId = order. OId, Address = order. address, OName = order. OName, Items = new System. collections. generic. list
()}; If (order. items. count = 1) {var product = productServiceClient. getProductByPid (order. items [0]. pid); // call a single interface for obtaining products if (product! = Null) {result. Items. Add (ConvertOrderItem (order. Items [0], product) ;}} else {List
Pids = (from item in order. Items select item. Pid). ToList (); var products = productServiceClient. GetProductsByIds (pids); // call the if (products! = Null) {result. items = ConvertOrderItems (products, order. items); // batch conversion of OrderItem type} return result;} private static OrderItem ConvertOrderItem (OrderService. orderItem orderItem, ProductService. contract. product product) {if (product = null) return null; return new OrderItem () {Number = orderItem. number, OitemId = orderItem. oitemId, Pid = orderItem. pid, Price = orderItem. price, Product = new Product () {Pid = product. pid, PName = product. PName, PGroup = product. PGroup, Production = product. production }};} private static List
ConvertOrderItems (List
Products, List
OrderItems) {var result = new List
(); OrderItems. forEach (item => {var orderItem = ConvertOrderItem (item, products. where (p => p. pid = item. pid ). firstOrDefault (); if (orderItem! = Null) result. Add (orderItem) ;}; return result ;}}}
If you think it is necessary to put the IOrderServiceClient and IProductServiceClient interfaces into the AnticorrsiveBase Base class.
. Convert the logical process and write it directly in the anti-corrosion layer method
For anti-corrosion layer design, if you do not have much code to convert and the business is relatively simple, I suggest writing Procedural Code directly to make it easier. It is relatively simple and convenient to directly use static extension methods for some reusable code. The biggest problem is that it is not conducive to continuous reconstruction in the future, and we cannot predict future business changes, however, we can use refactoring to solve this problem.
. Convert the logic to object, establish the encapsulation and reuse structure, and prevent further corruption
Correspondingly, the conversion code can be objectized to form anti-corrosion objects. Each object is specially used to process the data acquisition and conversion logic of a business point, if you have data sending logic, it will greatly benefit from anti-corrosion objects. After the visualization, you can directly subscribe to dependency injection events of related controllers, it is inconvenient for you to implement dynamic conversion, sending, and retrieval of Procedural Code.
6. Two dependence inversion design methods for anti-corrosion layer
Next, let's take a look at how to make the anti-corrosion objects do not interfere with automatic service calling and sending. We hope that the anti-corrosion objects will be completely transparent in carrying out their anti-corrosion responsibilities, I don't want it to bring much overhead to our implementation.
. Event-driven (anti-corrosion layer listening displays logical events)
We can use the event to implement the observer mode, so that the anti-corrosion layer object can listen to an event. When the event is triggered, it automatically processes an action instead of the manual call to be displayed.
namespace OrderManager.Anticorrsive{ public interface IOrderAnticorrsive { void SetController(OrderController orderController); OrderViewModel GetOrderViewModel(long oId); }}
The Order anti-corrosion object interface contains a void SetController (OrderController orderController). This method is used to enable the anti-corrosion object to automatically register events.
Public class OrderController: ApiController {private IOrderAnticorrsive orderAnticorrsive; public OrderController (IOrderAnticorrsive orderAnticorrsive) {this. orderAnticorrsive = orderAnticorrsive; this. orderantisponsive. setController (this); // set the Controller to the anti-corrosion object} public event EventHandler
SubmitOrderEvent; [HttpGet] public void SubmitOrder (OrderViewModel order) {this. SubmitOrderEvent (this, order );}}
In the controller, you only need to trigger an event whenever a service action occurs. Of course, the main function is to send data and query the methods of calling objects directly. Because the anti-corrosion object serves as a bridge to integrate with the background service, many background service methods may need to be called at the same time when the order is submitted, which makes it easier to process events.
////// OrderViewModel anti-corrosion object ///Public class OrderAnticorrsive: AnticorrsiveBase
, IOrderAnticorrsive {public void SetController (OrderController orderController) {orderController. SubmitOrderEvent + = orderController_SubmitOrderEvent;} private void handle (object sender, OrderViewModel e) {// The logic for submitting an order }}}
. Dependency Injection Interface
The dependency Injection Interface is completely used to isolate the controller from the anti-corrosion object. In the above code, I defined the interface in the anti-corrosion Object layer, that is to say, the anti-corrosion layer must be referenced in the project where the Controller object is located. When processing events and methods at the same time, it may seem a bit unappealing. There are both interfaces and methods. In fact, this is a balance, the more pure things, the more costly they will be.
If we define a pure dependency Injection Interface to implement the anti-corrosion object, a special method is required to trigger the event, because events that are not in this class cannot be triggered.
7. Summary
This article is a simple summary of the design concept of anti-corrosion layer architecture in the UI Layer. It has only one purpose and provides a reference. Thank you.
Author: Wang qingpei
Source:Http://blog.csdn.net/wangqingpei557
The copyright of this article is shared by the author and CSDN. You are welcome to repost it. However, you must retain this statement without the author's consent and provide the original article connection clearly on the article page. Otherwise, you will be entitled to pursue legal liability.