ArticleDirectory
- (1) command object
- (2) commandexecutor
- (3) command bus
Overview
Continue to reference the images in the previous article (from the UDI Dahan blog). Write operations in the UI will be encapsulated into a command and sent to the domain model for processing.
We follow the design philosophy of domain driven design. Therefore, all the business logic is only processed in the domain model, and the command will not carry the business logic. In commandCodeIt is nothing more than getting some aggregate root through repository, and then entrusting the operation to the corresponding domain object or domain service for processing.
Implementation
Implementation involves three things:
(1) command object
Command objects are used to encapsulate command data. Therefore, these objects are mainly attribute-oriented and have a few simple methods. However, note that these methods cannot contain business logic.
For example, user registration is a command, so we need a registercommand class, which is defined as follows:
Public Class Registercommand: icommand
{
Public String Email { Get ; Set ;}
Public String Nickname { Get ; Set ;}
Public String Password { Get ; Set ;}
Public String Confirmpassword { Get ; Set ;}
Public Gender gender { Get ; Set ;}
Public Registercommand ()
{
}
}
Each attribute of this class basically corresponds to an input in the registry form (for convenience, each of the above attributes is public set, but if there are not many attributes, the encoding is not affected, it is best to change all attributes to private set, and then pass the attribute values through the constructor ). When you click "register", a registercommand instance will be created in controller (assuming MVC is used as the presentation layer mode), set the corresponding value, and then call commandbus. send (registercommand), and then display the corresponding information to the user according to the execution. (Commandbus will be mentioned later)
(2) commandexecutor
The role of commandexecutor is to execute a command. For the registration example, we will have a class of registercommandexecutor, which has only one execute method and accepts the registercommand parameter:
Public Class Registercommandexecutor: icommandexecutor <registercommand>
{
Private Irepository <user> _ repository;
Public Registercommandexecutor (irepository <user> repository)
{
_ Repository = repository;
}
Public Void Execute (registercommand cmd)
{
If (String. isnullorempty (CMD. Email ))
Throw New Invalidoperationexception ( " Email is required. " );
If (CMD. Password! = Cmd. confirmpassword)
Throw New Invalidoperationexception ( " Password not match. " );
// Other "command parameter" Validations
VaR Service = New Registrationservice (_ repository );
Service. Register (CMD. Email, cmd. Nickname, cmd. Password, cmd. Gender );
}
}
In the execute method, we need to verify the correctness of the command first, but note that the verification here only verifies whether the data in registercommand is legal, not the business logic. For example, the system will verify whether the email address is empty and the email address format is correct. However, if the email address format is correct, registration is not allowed because the system may require adults over 18 years old to register, this is the business logic. registrationservice is responsible for ensuring that all business rules are not damaged. registrationservice is a domain service and exists in the domain model.
As you can see, commandexecutor mainly involves two parts: one is to verify whether the input command object is valid, and the other is to call the domain model to complete the operation. The command mentioned in the previous article is a conceptual level command. It not only refers to the command in (1), but also contains (1) and (2.
PS:I remember that when I struggled with the "three-tier architecture" before year 34, what I don't understand most should be "business logic". Now I seem to understand a little bit. The key word in "business logic" is "business", which is also a key factor for distinguishing it from other logics, such as application logic,If a logic carries "business value", it will be "business" logic, otherwise it will not be counted. For example, if the number of refunds for an order exceeds 100, the order cannot be placed. This is the business logic. However, "the passwords entered twice at registration must be consistent" is not the business logic. But I still have a problem. Do I have to make sure that the email is unique and does not count as a business logic? I personally tend to think of it as business logic. Is the mail format correct (that is, the @ symbol must be included in the middle) to calculate the business logic? I personally think it is not counted. If it is not regarded as the business logic, do domain models need to be verified? Individuals tend not to validate in a domain model. These logics should be verified in commandexecutor. What do you think?
(3) command bus
Commandexecutor is used to execute commands, but commandexecutor is not used to call commands at the UI Layer. Only the command object and the command bus to be mentioned are used in the UI Layer. Command bus distributes a command to the corresponding commandexecutor for execution. When developing the UI Layer, we don't need to worry about which executor will execute the command, but as long as we know that God has given us a commandbus, we only need to create a command object and throw it, the magic commandbus will help us complete the execution. In this way, for the development of the UI Layer, the concept involved is very simple and there are few classes involved. Most of the work is to get the input in the form, encapsulate it into a command object, and throw it to commandbus.
The following is the controller of the registered example:
Public Class Accountcontroller: Controller
{
[Httppost]
Public Actionresult register (registercommand command)
{
If (Modelstate. isvalid)
{
Try
{
Commandbus. Execute (command );
Formsauthentication. setauthcookie (command. email, False );
Return Redirecttoaction ( " Index " , " Home " );
}
Catch (Exception ex)
{
Modelstate. addmodelerror ( " Error " , Ex );
}
}
Return View (command );
}
}
The implementation of commandbus is also very simple. First, let commandexecutor all implement a generic interface:
Public InterfaceIcommandexecutor <tcommand>
WhereTcommand: icommand
{
VoidExecute (tcommand cmd );
}
Here, icommand is an empty interface without any method (namely, marker interface). Its function is to implement the compile-time constraints, so that we can limit that all input commandexecutor objects are command objects, instead of accidentally passing the wrong user object (all command objects must implement the icommand interface ).
Then, write commandbus as follows:
Public Static ClassCommandbus
{
Public Static VoidSend <tcommand> (tcommand cmd)WhereTcommand: icommand {
VaRType =Typeof(Tcommand );
VaRExecutortype = findexecutortype (type );
VaRExecutor = activator. createinstance (executortype );
Executor. Executor (CMD );
}
}
In this send method, we obtain the executor class of the specific type of the command object passed in by the generic parameter through reflection, and then call its execute method. The above code is pseudocode. In actual implementation, we can use the IOC framework to simplify this process. In addition, we can make some improvements, such as designing commandbus as one of the extension points. In addition, we can control the life cycle of unitofwork (equivalent to idbcontext in normal entityframework, and datacontext in LINQ 2 SQL) in commandbus.
The complete commandbus code is as follows (a small amount of pseudocode is still available ):
Public InterfaceIcommandbus
{
VoidExecute <tcommand> (tcommand cmd)WhereTcommand: icommand;
}
Public ClassDefaultcommandbus: icommandbus
{
Public VoidSend <tcommand> (tcommand cmd)WhereTcommand: icommand
{
Unitofworkcontext. startunitofwork ();
VaRExecutor = objectcontainer. Resolve <icommandexecutor <tcommand> ();
Executor. Execute (CMD );
Unitofworkcontext. Commit ();
}
}
Other codes are not pasted in the Document. All codes can be downloaded at the end of the article.
In this way, we have completed a basic implementation of command in cqrs.
Notes
(1)Command indicates the command to be executed, so the Class Name of the command class should be in the form of a verb. For example, registercommand and changepasswordcommand. However, the command suffix is optional, as long as it can be consistent.
(2)The command and commandexecutor correspond one to one.. That is to say, a command corresponds to only one commandexecutor, which is different from the subsequent events. The events are one-to-many, and one event can correspond to multiple eventhandler.
(3) The "command" object also plays the role of "DTO" (data transfer object, which is called "view model" in this example, this is also one of the reasons that the command and executor are separated and the execute method is not directly written in the Command class.
(4) Pay attention to the important role of the command class name. The name of each command class clearly expresses an intent. For example, changepasswordcommand clearly expresses that the command is to change the password, soNever "reuse" command at willHere, "reuse" refers to the fact that a certain two commands have identical attributes, and it is unnecessary to use two commands and merge them into one, this "reuse" will make the system more difficult to understand, although it may indeed reduce several lines of code.
(5) The command is generally described by "send", while the event is described by "publish". Therefore, the method name in commandbus personally thinks that it is appropriate to use "send, instead of publish.
Code download
Http://files.cnblogs.com/mouhong-lin/CQRS.zip
Note:The downloaded code is not exactly the same as the code in the article, but there is no big difference. In the sample code, only the command and user registration functions are implemented. Other functions, such as events, are not included.
PS: what I fear most about the writing of technical articles is that my understanding is biased, which leads to negative effects, I did not discuss it if I did not write it. Tonight, I suddenly think of a suggestion that I feel better: if I feel that a certain sentence or opinion is inaccurate during reading, I can comment on it, after that, the author does not delete the original sentence (cut out the original sentence with a line), which can make the article more rigorous, at the same time, we will clearly see which points have been corrected.