Part 1ArticleI explained the cross-cutting section of the nlayerapp case. Now, let's continue to explain the basic layer (data access part) of the nlayerapp ). The basic structure (Data Access) of the nlayerapp includes the following content:Unit of work(Poeaa), warehousing implementation, nlayerapp data model, and test-related classes. Next, we will discuss the first three parts and test-related content. I plan to introduce them in a separate chapter.
Unit of work (poeaa)
unit of work (uow) mode is widely used in enterprise application architecture. It can collect object state changes in domain model, at the same time, the change of the object is committed to the data in the same database connection and transaction processing context. Before uow is introduced, you can directly call the database to save the changes of objects after each addition, deletion, or object state change, however, this will cause the application Program to frequently access the external technical architecture of the database, seriously affecting the system performance. This is like opening notepad for text editing. We can press Ctrl + S to save every character input, but it is very time-consuming (and unnecessary ), the common practice is to save a paragraph every time it is edited (input, delete, or change, in this case, the notepad will track the paragraph and its character changes while editing the paragraph, and write the changes to the hard disk at one time. From the description of the uow mode, it is a bit like a database transaction because they all have "commit" and "rollback" operations. However, in terms of semantics, It is not equivalent to database transactions. I think we should understand this: we can regard uow as a transaction object, but it is not a database transaction. Its transaction is reflected in the ability to submit objects to the persistence mechanism at one time in an atomic operation, or, if a problem occurs during the submission process, it can return the object to the status before submission. In addition, uow also supports tracking changes in domain objects. It can track changes in domain objects within the scope of a business step, as in the above example, the editing of each section can be regarded as a business step. In this business step (during the paragraph editing process), uow will track domain objects, when the business step is completed (when the paragraph editing is completed), uow will submit the tracked changes at one time.
From the above analysis, we can see that uow, like warehousing, should belong to the domain model, its design should be technically unrelated (that is, Poco or ipoco), because it tracks the changes of domain objects in the domain model. Of course, A better design should be to useSeparated Interface(Poeaa) mode. The uow interface and the warehousing interface are designed in the domain model together. From the implementation of uow, nlayerapp adopts some features of Entity Framework and uses T4 to generate Automatic Generation Based on the Entity Framework Model.Code. Currently, we do not need to care about how to use T4 to generate the code in nlayerapp. What we need to care about is why we need to generate the code. We will not discuss the model project, domain specific language (DSL), and T4 Code Automatic Generation in Visual Studio. If you are interested, refer to my previous article 《Use the modeling project in Visual Studio 2010 to customize DSL and automate code generation. The following is the uow-related class relationship diagram in nlayerapp:
Before learning about the uow execution mechanism of nlayerapp, let's take a look at the three uow-related interfaces in nlayerapp.
- Iobjectwithchangetracker Interface
Only one objectchangetracker attribute is defined under this interface. In nlayerapp, all objects must implement the iobjectwithchangetracker interface to provide objectchangetracker instances to the outside world (mainly uow and warehousing. The main function of objectchangetracker is to record the state changes in the current object. For example, the current state of an object, the raw data of all attributes before the change, all objects added to the set attribute, and all objects deleted from the set attribute. When a warehouse registers a changed entity through unit of work, the unit of work registers the Entity Framework with the information provided by objectchangetracker.
-
- Inotifypropertychanged Interface
The nlayerapp entity not only implements the iobjectwithchangetracker interface, but also implements the inotifypropertychanged interface. The main purpose of this interface is to promptly record this change in objectchangetracker when a property of an object changes. Therefore, as long as the client program changes the state of an object through the object attributes, the object itself records the state changes to objectchangetracker.
- Irepository Interface
The irepository interface is an interface defined at the domain model layer. It is mentioned here because the object persistence process is completed through warehousing, and persistence is inseparable from uow. In nlayerapp, The irepository interface has an iunitofwork attribute. Therefore, all warehouses must implement this attribute so that repository can record object change information in uow. From nlayerappSource codeAs you can see, the warehousing itself is not responsible for the specific task of saving entities to the database. It only sets the objects to be saved to the corresponding State through the iobjectwithchangetracker interface, and register the object with uow for changes. The remaining tasks dealing with databases are completed by uow.
With this information, we can understand that all entities in the nlayerapp manage their own change records, which are called self-tracking entities (STE ). In fact, from the perspective of DDD, Ste is not a good design because it brings too many technical concerns to the domain model. For example, when ste is implemented, when you add an order to the customer, you must first determine whether the order has been marked as "deleted" in the objectchangetracker of the customer, if so, you need to remove the order from the "delete" List of objectchangetracker. Business Logic similar to this should not be placed in domain model. In addition, in order to cater to the requirements of Entity Framework, nlayerapp implements ste not purely unrelated to technology. The same is true for uow. For example, in the preceding class diagram, we can see that mainmoduleunitofwork is a subclass of objectcontext.
Now let's link up our ideas. Let's take the customer modification as an example. Let's start with the distributed service layer on the server of the entire architecture to see how unit of work works with warehousing.
1. distributedservices. mainmodule project: The mainmoduleservice class changes the customer information by using customermanagementservice at the application layer:
Public void changecustomer (customer) {try {// resolve root dependency and perform Operation icustomermanagementservice customerservice = iocfactory. instance. currentcontainer. resolve <icustomermanagementservice> (); customerservice. changecustomer (customer);} catch (argumentnullexception ex ){//......}}
The above Code uses iocfactory to obtain the specific implementation of icustomermanagementservice from the IOC container. For the implementation of the IOC container in the nlayerapp, see the previous article.
2. Application. mainmodule project: The customermanagementservice class implements the icustomermanagementservice interface and implements the changecustomer method. In this method, the uow is obtained through the unitofwork attribute of customerrepository, And the modify method of the warehouse is called to register the customer entity to the uow to be changed, and the state of the customer entity is changed. Finally, use the commitandrefreshchanges method of uow to submit the changed object to the database:
Public void changecustomer (customer) {If (customer = (customer) null) throw new argumentnullexception ("customer"); iunitofwork unitofwork = _ customerrepository. unitofwork as iunitofwork; _ customerrepository. modify (customer); unitofwork. commitandrefreshchanges ();}
It is worth mentioning that in customermanagementservice, customerrepository obtains instantiation in the form of constructor injection:
/// /// create new instance /// /// customer repository dependency, /// intented to be resolved with dependency injection // country repository dependency, /// intended to be resolved with dependency injection Public mermermanagementservice (icustomerrepository merrepository, icountryrepository countryrepository) {If (customerrepository = (condition) null) throw new partition ("customerrepository"); If (countryrepository = (callback) null) throw new partition ("comment"); _ customerrepository = customerrepository; _ countryrepository = comment ;}
3. Infrastructure. data. core Project: The modify method of the Repository class first sets the object whose current status is not deleted as "modified". At the same time, in the uow, The registerchanges call is used to register the object with the uow:
Public Virtual void modify (tentity item) {// check arguments if (item = (tentity) null) throw new argumentnullexception ("item", resources. messages. prediction_itemargumentisnull); // set modifed state if change Tracker Is enabled and state is not deleted if (item. changetracker! = NULL & (item. changetracker. State & objectstate. Deleted )! = Objectstate. deleted) {item. markasmodified ();} // apply changes for item object _ currentuow. registerchanges (item); _ tracemanager. traceinfo (string. format (cultureinfo. invariantculture, resources. messages. trace_appliedchangeditemrepository, typeof (tentity ). name) ;}
4. Infrastructure. Data. mainmodule project: The registerchanges method of the mainmoduleunitofwork class simply uses the mechanism provided by Entity Framework to register object status changes with Entity Framework. This is the detailed implementation of Entity Framework technology. We will not go into the implementation method here:
Public void registerchanges <tentity> (tentity item) Where tentity: Class, iobjectwithchangetracker {This. createobjectset <tentity> (). applychanges (item );}
5. Infrastructure. Data. mainmodule project: The commitandrefreshchanges method of the mainmoduleunitofwork class submits the change to the database through the Entity Framework, and sets the state of the object to "not changed ":
Public void commitandrefreshchanges () {try {// default option is detectchangesbeforesave base. savechanges (); // accept all changes in STE entities attached in context ienumerable <iobjectwithchangetracker> steentities = (from entry in this. objectstatemanager. getobjectstateentries (~ Entitystate. Detached) where entry. entity! = NULL & (entry. entity as iobjectwithchangetracker! = NULL) select entry. entity as iobjectwithchangetracker); steentities. tolist (). foreach (STE => Ste. markasunchanged ();} catch (optimisticconcurrencyexception ex ){//......}}
We can use the following sequence diagram to represent the entire execution process:
Unit of work in nlayerapp we will introduce it here first. If you have any questions, you can comment on it.
Implementation of warehousing
The warehousing implementation in the nlayerapp is also an important component of the infrastructure layer (data access part), which is consistent with the classic architecture style of DDD. In theory, the specific implementation of warehousing depends on external systems, and this part of content cannot be exposed to the domain model layer. That is, what we usually call persistence ignorance. Nlayerapp first designs a generic repository for all entities (specifically, it should be an aggregate root). You can create a generic repository in infrastructure. data. find the source code of this generic repository in the core project, which implements all the basic functions that a repository should have, such as adding, deleting, modifying object objects, and querying operations based on the conventions; then, for some aggregated roots, nlayerapp will implement some specific operations in the warehouse according to the actual needs of the project. For example, customerrepository inherits from the general repository, and implements the icustomerrepository interface to provide external users with the ability to query customer information through specification. This design achieves separation of focus to a certain extent. For example, when we perform general warehousing operations on entities, we only need to obtain the specific implementation of the irepository interface, instead of using icustomerrepository to obtain the warehousing implementation related to the customer. About icustomerrepository andSeparation of concernsIn the next section (domain model layer.
The following is the class relationship diagram of the nlayerapp repository, which is provided here for your reference.
The nlayerapp's warehousing implementation also uses a lot of technical details related to the Entity Framework, such as objectset. These are the specific technical implementation content, which will not be described here. If you are interested, please refer to the documentation related to Entity Framework technology.
Data Model of nlayerapp
Nlayerapp uses the ADO. NET Entity Data Model Designer of Entity Framework to design the data model, which enables us to have a very intuitive understanding of the object structure of the entire domain model. The data model is located in the infrastructure. data. under the mainmodule project, double-click mainmoduledatamodel. edmx can be opened in the designer, and the relationship between the object structure and the object can be clearly displayed in front of you. You will find that in the background code file of this data model, there is no substantive content except some annotations, because nlayerapp only uses this designer to design the data model, the real domain model code is automatically generated using T4 Based on the Data Model in the domain model layer. For details, see domain. mainmodule. entities project. This also makes us think about the tangle of the question: does Entity Framework provide us with a database-oriented data model or a domain-driven domain model? In practical applications, we may place it more on the orm location, so the Entity Data Model becomes the entity object located between the domain model object and the database.Row Data Entry(Row data gateway, poeaa ). I have written some articles on Entity Framework-Based Domain-driven design practices. For readers, refer 《Summary of articles in the field-driven design Series.
Summary
This article analyzes and introduces the basic structure (Data Access) of nlayerapp, especially the implementation of unit of work. In the next lecture, we will learn the domain model section of nlayerapp.
Read more
- Unit of work: enterprise application architecture model (poeaa)
- Separated interface mode: enterprise application architecture mode (poeaa)
- Row data Gateway: enterprise application architecture model (poeaa)
- Repository model: enterprise application architecture model (poeaa)
- Separation of concerns (separation of concerns)