Network Security Internet Technology Development Database Servers Mobile Phone Android Software Apple Software Computer Software News IT Information

In addition to Weibo, there is also WeChat

Please pay attention

WeChat public account

Shulou

Example analysis using ENode 2.0

2025-01-18 Update From: SLTechnology News&Howtos shulou NAV: SLTechnology News&Howtos > Development >

Share

Shulou(Shulou.com)06/02 Report--

This article introduces the knowledge of "using ENode 2.0 examples to analyze". In the operation of actual cases, many people will encounter such a dilemma. Next, let the editor lead you to learn how to deal with these situations. I hope you can read it carefully and be able to achieve something!

ENode, EQueue, Forum open source project address

ENode open source address: https://github.com/tangxuehua/enode

EQueue open source address: https://github.com/tangxuehua/equeue

ECommon open source address: https://github.com/tangxuehua/ecommon

Forum open source address: https://github.com/tangxuehua/forum

Online address of Forum forum (temporary domain name, which will be changed to enode.me later): http://enode.cloudapp.net

Forum Forum equeue message data Monitoring Statistics Page: http://enode.cloudapp.net/equeueadmin

In addition, if you want to develop a reference assembly in a project, you can get it through Nuget. Enter the keyword ENode and you can see all the relevant Package, as shown in the following figure:

Analysis of overall Architecture of Forum

Forum uses the DDD+CQRS+Event Sourcing architecture. With the help of ENode, Forum itself no longer needs to design the technical architecture, but can directly use ENode to complete this architecture. So as long as we understand the architecture of ENode, we will know what the architecture of Forum is. The following is the architecture diagram of ENode (for those who have already understood this diagram, please skip this section directly):

The figure above is a data flow diagram of the CQRS architecture. UI requests fall into two categories: Command and Query.

Command is used to write data, Query is used to read data, write data and read data are implemented in completely different architectures. Writing data supports synchronous and asynchronous mode, and reading data is realized by simple and efficient way of thinking. When we want to write to the system, if you are using ASP.NET MVC to develop the site, you can create and send a Command in Controller. The Command is sent to the message queue (EQueue), and then the subscriber of the message queue, that is, the process dealing with the Command, pulls the Command, and then calls Command Handler to complete the processing of the Command; when the Command Handler processes the Command, it calls the Domain method to complete the relevant business logic operation. Domain is the domain layer in DDD, which is responsible for implementing the business logic of the whole system. Then, because of the architecture of Event Sourcing, any modification of the aggregation root in Domain will produce corresponding domain events (Domain Event). Domain events will be persisted to EventStore first, and if persistence is successful without concurrency conflicts, it will be published (Publish) to the message queue (EQueue), and then the subscribers of the message queue, that is, the process of processing Domain Event, will pull these Domain Event Then call the relevant Event Handler to do relevant updates, for example, some Event Handler will update the Read DB, and some will generate a new Command, which I call the process manager (Process Manager, also known as Saga). When we sometimes need to modify multiple aggregation roots in a business scenario, we need to use Process Manager. Process Manager is responsible for modeling the process, and its principle is based on event-driven process implementation. The Process Manager handles the event and then produces the Command of the response, thus completing the interaction between the aggregation roots. In general, for a process, we design a process aggregation root and other aggregation roots that participate in the process, and Process Manager is used to coordinate the interaction between these aggregation roots. For a specific example, take a look at BankTransferSample in the ENode source code.

On the query side, because they are all queries, these queries are used for the purpose of displaying data in UI or providing data for third-party interfaces, and the query has no side effects on the system. We can implement the Query side in any way we like. The query is directed at Read DB. As mentioned above, the data in Read DB is updated through Event Handler (foreigners are called Denormalizer).

So we can see that throughout the architecture, the data sources on the Command side and the query side are completely separate. The result of * on the Command side is that Domain Event,Domain Event is persisted in Event Store; the data source on the query side is Read DB, which can be stored in a relational database. The data synchronization between the two ends of CQ is realized by Domain Event.

The advantage of the CQRS architecture shown above is that both read and write are separated at the architecture level and the data storage level. In this way, we can easily optimize reading or writing separately. In addition, due to the use of the Event Sourcing architecture, as long as our Command side persists the Domain Event, it means that all the states of the Domain are saved. This feature allows our framework to have a lot of room for design, for example, we do not have to consider the strong consistency between Domain Event and business data, because Domain Event itself is the business data itself, and we can restore the state of Domain at any time through Domain Event. When we want to query the current * * data of Domain, we can go to Query. Of course, because the query side is updated asynchronously, the data on the query side may be a little late. This is the ultimate consistency that we always talk about (the data on both sides of the CQ will eventually be the same).

From the architecture diagram above, we know that after a Command is issued, it will go through two stages: 1) it is first processed by a Command Service (calling Domain to complete the business logic to generate Domain Event); 2) it is then processed by Event Service (responding to Domain Event, completing the update of Read DB or generating a new Command); understanding these two phases is useful for understanding the project structure of the following Forum.

Analysis of Forum Project structure

The above is the project structure of Forum. The project consists of four host projects, namely:

Forum.BrokerService:

This project is used to host the Broker of EQueue, and all Command,Domain Event messages in the entire forum will be placed on Broker. For example, the Command sent by Controller will be sent to Broker, and the Domain Event generated by Domain will also be sent to Broker;, and consumer consumption messages will be pulled from BrokerService. Since the hosting project does not need to interact with the user, I deployed as Windows Service.

Forum.CommandService:

This project is the process used to deal with Command and is also deployed as Windows Service.

Forum.EventService:

This project is the process used to deal with Domain Event and is also deployed as Windows Service.

Forum.Web:

This is the Web site of the forum, needless to say; what this Web site does is send Command or call the query service on the query side to query data; Web site only needs to rely on Forum.Commands and Forum.QueryServices, because it only needs to send Command and query data.

Forum.CommandHandlers:

All Command Handler are in this project. The responsibility of Command Handler is to handle Command and call the method of Domain to complete the business logic.

Forum.Commands:

All Command are in this project, and each Command is a DTO that is encapsulated as a message and sent to EQueue.

Forum.Domain:

It is the domain layer of the forum, and all aggregation, factories, domain services, and domain events are in this project. This project is the most valuable part of the whole Forum. It is the project where the business logic is located.

Forum.Domain.Dapper:

Because some interfaces may be defined in Domain, the persistence behind these interfaces needs to be implemented externally; if according to the classical DDD architecture, such as the warehouse interface is defined in the Domain layer, and the implementation is in the Infrastructure layer. From the hierarchical architecture diagram of the classic DDD, the Domain layer depends on the Infrastructure layer, but there are some warehousing implementation classes in the Infrastructure layer that depend on the Domain layer; although I can understand this two-way dependency, it is easy to bring confusion to many people who learn DDD, so I am more inclined to regard Domain as the core of the architecture, while everything else is the periphery of Domain. This idea is actually the same as the hexagonal structure. From an architectural point of view, it is not that the upper layer depends on the lower layer, but the outer layer depends on the inner layer; the inner layer defines the interface, the outer layer implements the interface, and the inner layer only needs to face the interface defined by itself. So based on this idea, I will take the interface defined in Forum.Domain, if it is implemented in Dapper, then I will define a project like Forum.Domain.Dapper, which means that the implementation of Forum.Domain.Dapper depends on the inner Forum.Domain. If we have an implementation based on EntityFramework in the future, we just need to create another project like Forum.Domain.EntityFramework. Therefore, it can be seen that the driver of the Forum.Domain.Dapper project is the Forum.Domain-to-external adapter, the adaptation interface is defined in Forum.Domain, and the project Forum.Domain.Dapper implements these adaptation interfaces. Based on this idea, our architecture does not have the concept that the upper layer depends on the lower layer, but is replaced by the relationship between the inner layer and the outer layer, the inner layer does not depend on the outer layer, the outer layer depends on the inner layer, and the inner layer and the outer layer interact directly through the adapter interface, or through Domain Event. In this way, we don't have to worry about the seemingly two-way dependency in the classic DDD.

Forum.Domain.Tests:

This project is a test project for Forum.Domain. Each test case simulates that the Controller initiates the Command, and then * checks whether the state in the Domain is modified correctly.

Forum.QueryServices:

This project defines all the query interfaces on the query side, and the Forum.Web site depends on the query service interface in the project; then the implementation of these query interfaces is placed in Forum.QueryServices.Dapper. The relationship between Forum.QueryServices and Forum.QueryServices.Dapper is similar to that between Forum.Domain and Forum.Domain.Dapper, so it will not be repeated here.

Forum.Denormalizers.Dapper:

What happens in this project is that all the Denormalizer,Denormalizer is responsible for processing the Domain Event and then updating the library. Then, since Dapper is currently used for data persistence, the project name ends with Dapper.

Forum.Infrastructure:

This is a basic project that stores all the basic common things, such as some business-independent services or configuration information or global variables; it should be emphasized that the Forum.Infrastructure here is not the same concept as the Infrastructure in the classic DDD. Infrastructure in DDD is a logical layer, and all the technical support implementations in the domain layer are in Infrastructure, while Infrastructure here is just something common to the foundation of Common. Infrastructure is not intended to serve any other layer, but can be used by any other project.

All right, the above briefly introduces the function and design purpose of each project. Let's take a look at the design of Forum's domain model.

The design of Domain Model for Forum

Core functional requirements analysis:

Provide three functions of user registration, login and logout; you need to verify whether the user name is unique when registering a user.

Provide basic core functions such as posting, replying, modifying posts, modifying replies, and replying.

System administrators can maintain the forum section

Aggregation identification: the aggregations identified are: forum account, post, reply, and forum.

Then analyze each aggregate of the information we care about: the minimum information of the account should be: account name + password; the section should have a name; the post should have the title, content, sender, posting time, and the section to which it belongs; reply should have reply content, reply time, reply person, the section to which you belong, parent reply (can be empty)

Scene check: registration is the creation of an account (the design of account uniqueness is analyzed in detail later); the essence of login is to call the query service on the query side to find out whether the account exists, so there is no need for Domain to do anything, and logout is also; posting is to create a post; reply is to create a reply; to modify a post is to modify the aggregate root of the post; to modify a reply is to modify the aggregate root of the reply. To add a forum is to create an aggregate root of the forum.

Identification of key business rules: 1) the account name must not be repeated; 2) the post must have its own section and poster; 3) the reply must have a corresponding post and responder.

Implementation of key business rules:

How to realize that the account name can not be duplicated? First of all, it is a business rule, so it must be implemented in Domain, not in Command Handler. Then, due to the architecture of Event Sourcing, there is an inherent defect that the requirement of uniqueness constraint cannot be achieved. So we need to explicitly design something in Domain that can express the aggregate root index. I call them IndexStore, which means that it is a storage of the aggregate root index. This idea is very similar to that in the classic DDD, where we have the concept of Repository, which maintains all the aggregate roots; my IndexStore here maintains the index information of the aggregation roots. With this index information, we can design a domain service such as RegisterAccountService in Domain when registering a new account. In the domain service, AccountIndexStore is used to check whether the account name is duplicated. If not, the current account name is added to the AccountIndexStore. If the account is duplicated, an exception is reported. Another non-business point to consider is how to implement the processing of concurrent registered users. We can implement db-level locks in Command Handler (instead of locking the entire account table, but locking a record in one other table) to ensure that no two Account names are added to the AccountIndexStore at the same time; we explicitly express the business rule that "account names cannot be duplicated" through RegisterAccountService, thus implementing this business rule in the code-level domain. In the past, if we did not use Event Sourcing, we might rely on the unique index of db to achieve this uniqueness, although it can be achieved functionally, but in fact, the business rule that account names can not be repeated is not reflected in the domain. This is what I thought of this time by implementing uniqueness verification based on Event Sourcing.

Posts must belong to the section and the person who posted them. This business rule is easy to guarantee, as long as you judge whether the sections and posts are empty on the aggregation root of the posts.

The reply must have a corresponding post and responder, and the same is true, as long as you judge whether it is empty in the constructor

Take registering a new user as an example to show the code implementation

The client JS submits registration information through angularJS:

$scope.submit = function () {if (isStringEmpty ($scope.newAccount.accountName)) {$scope.errorMsg = 'Please enter the account number.' ; return false;} if (isStringEmpty ($scope.newAccount.password)) {$scope.errorMsg = 'Please enter your password.' ; return false;} if (isStringEmpty ($scope.newAccount.confirmPassword)) {$scope.errorMsg = 'Please enter a password to confirm.' ; return false;} if ($scope.newAccount.password! = $scope.newAccount.confirmPassword) {$scope.errorMsg = 'password entry is inconsistent.' ; return false } $http ({method: 'POST', url:' / account/register', data: $scope.newAccount}) .success (function (result, status, headers, config) {if (result.success) {$_ window.location.href ='/ home/index' } else {$scope.errorMsg = result.errorMsg;}) .error (function (result, status, headers, config) {$scope.errorMsg = result.errorMsg;});}

Controller handles requests:

[HttpPost] [AjaxValidateAntiForgeryToken] [AsyncTimeout (5000)] public async Task Register (RegisterModel model, CancellationToken token) {var command = new RegisterNewAccountCommand (model.AccountName, model.Password); var result = await _ commandService.Execute (command, CommandReturnType.EventHandled) If (result.Status = = CommandStatus.Failed) {if (result.ExceptionTypeName = = typeof (DuplicateAccountException) .Name) {return Json (new {success = false, errorMsg = "this account has been registered, please register with another account." });} return Json (new {success = false, errorMsg = result.ErrorMessage});} _ authenticationService.SignIn (result.AggregateRootId, model.AccountName, false); return Json (new {success = true});}

CommandHandler handles Command:

[Component (LifeStyle.Singleton)] public class AccountCommandHandler: ICommandHandler {private readonly ILockService _ lockService; private readonly RegisterAccountService _ registerAccountService; public AccountCommandHandler (ILockService lockService, RegisterAccountService registerAccountService) {_ lockService = lockService; _ registerAccountService = registerAccountService } public void Handle (ICommandContext context, RegisterNewAccountCommand command) {_ lockService.ExecuteInLock (typeof (Account). Name, () = > {context.Add (_ registerAccountService.RegisterNewAccount (command.Id, command.Name, command.Password);});}}

RegisterAccountService Domain Services:

/ / provide domain services for account registration, encapsulating the business rules of account registration, such as account uniqueness check / [Component (LifeStyle.Singleton)] public class RegisterAccountService {private readonly IIdentityGenerator _ identityGenerator; private readonly IAccountIndexStore _ accountIndexStore; private readonly AggregateRootFactory _ factory Public RegisterAccountService (IIdentityGenerator identityGenerator, AggregateRootFactory factory, IAccountIndexStore accountIndexStore) {_ identityGenerator = identityGenerator; _ factory = factory; _ accountIndexStore = accountIndexStore } / / sign up for a new account / public Account RegisterNewAccount (string accountIndexId, string accountName, string accountPassword) {/ / first create a new account var account = _ factory.CreateAccount (accountName, accountPassword) / / first determine whether the account exists var accountIndex = _ accountIndexStore.FindByAccountName (account.Name); if (accountIndex = = null) {/ / if it does not exist, add it to the account index _ accountIndexStore.Add (new AccountIndex (accountIndexId, account.Id, account.Name)) } else if (accountIndex.IndexId! = accountIndexId) {/ / if it exists but is different from the current index ID, the account is considered to have a duplicate throw new DuplicateAccountException (accountName);} return account;}}

EventHandler handles Domain Event:

Public class AccountEventHandler: BaseEventHandler, IEventHandler {public void Handle (IEventContext context, NewAccountRegisteredEvent evnt) {using (var connection = GetConnection ()) {connection.Insert (new {Id = evnt.AggregateRootId, Name = evnt.Name) Password = evnt.Password, CreatedOn = evnt.Timestamp, UpdatedOn = evnt.Timestamp, Version = evnt.Version}, Constants.AccountTable) This is the end of the content of "example Analysis using ENode 2.0". Thank you for your reading. If you want to know more about the industry, you can follow the website, the editor will output more high-quality practical articles for you!

Welcome to subscribe "Shulou Technology Information " to get latest news, interesting things and hot topics in the IT industry, and controls the hottest and latest Internet news, technology news and IT industry trends.

Views: 0

*The comments in the above article only represent the author's personal views and do not represent the views and positions of this website. If you have more insights, please feel free to contribute and share.

Share To

Development

Wechat

© 2024 shulou.com SLNews company. All rights reserved.

12
Report