1. Introduction
Eshoponweb is based on the ASP. NET core build, the official creation of such a sample project, I would like to be nothing more than the following:
- Promote ASP. NET Core
- Guidance on how to design your architecture with ASP.
- Popularize the idea of architecture design
Eshoponweb and the other eshoponcontainers complement each other. Eshoponcontainers is an application architecture based on microservices and container technology that supports multiple deployments. The eshoponweb is much simpler than it is based on traditional Web application development and only supports single deployment.
This article simply combs down what you have learned.
2.MPA Or SPA
Eshoponweb's sample project contains two Web projects, one based on the MPA multi-page application created by MVC, and one based on the Spa single page app created by Razor. How do I choose between this?
- Is there a need for rich interaction behavior?
- Is there enough front-end technology to accumulate?
- Do you primarily interact with the API?
3. Architecture Design
Part of the idea of using DDD and neat architecture in Eshoponweb is worth a look.
3.1 Architectural Principles
separation of concerns : referred to as SOP. In the layered architecture design, the separation of concerns is the core design idea, each layer is responsible for the different responsibilities alone. Architecturally, it can be achieved by separating the core business from the infrastructure and the user interface logic. This principle is designed to avoid tight coupling and to ensure independent development of individual modules.
Package : What is the package? Is the state and behavior of the object. External objects do not need to focus on their internal implementation mechanisms.
In a class, encapsulation is implemented by restricting external access by using the access modifier. If the external wants to manipulate the state of an object, it should be implemented through well-defined functions (or property sets) rather than directly accessing the private state of the object.
The encapsulation is implemented by means of a public well-defined interface between different modules. To isolate the internal implementation mechanism. Encapsulation is used to ensure isolation between different parts of the application, and proper encapsulation helps to achieve loose coupling and modularity in application design.
dependency inversion : short dip. High-level modules should not be dependent on the lower-layer modules, should be dependent on the abstraction, the abstraction should not depend on the details, the details should be dependent on abstraction. Dip is a key part of building loosely coupled applications, ensuring that applications are modular and easier to test and maintain. Dependency injection can be applied by following the dip.
Explicit dependencies : Methods and classes should explicitly specify the required collaboration objects (dependencies) to ensure proper operation. Simply put, for a class, provide an explicit constructor (that is, specify the dependent objects that the class needs to work properly in the constructor arguments) so that the caller correctly passes the arguments to instantiate the object correctly.
single Duty : the SRP for short. As one of the principles of object-oriented design, SRP also applies to architectural principles. It is similar to SOP. It emphasizes that objects should have only one responsibility, and that they should only have one reason for change. In other words, the only thing the object should change is that its responsibilities need to be updated. By adhering to this principle, you can write loosely coupled and modular applications. Because a lot of new behavior should be created by creating a new class to implement, instead of being added to a class that already exists. Adding a new class is always safer than modifying a class, because no code is yet dependent on the new class.
In complex, large-scale applications, SRP can be applied to layers of tiered applications. The presentation responsibility should remain in the UI project, and the data access responsibility should remain in the infrastructure project, and the business logic should remain in the application core project. This means that it is easy to test and can evolve independently of other responsibilities.
The more advanced application of this principle is microservices. Each micro-service is responsible for independent duties.
Discard Duplicates : When duplicates occur, refactoring should be implemented. Avoid the need to modify multiple parts at the same time when the feature is improved.
transparent Persistence : requires the ability to easily switch persistence technology, while achieving persistence without awareness (transparent persistence).
Clearance Context : This concept is part of the DDD strategy design, which divides the domain through the bounds context, as an explicit boundary for the domain, provides context for the domain, guarantees that some terminology within the domain, business-related objects, etc. (common language) has a definite meaning, without ambiguity.
3.2. Traditional tiered architecture and clean architecture
The traditional layered architecture is a well-known three-tier architecture.
The disadvantages of this architecture are:
- Dependencies from top to bottom, not easy to decouple
- Not easy to test, need to test database
How to solve the problem of the three-tier architecture, with the help of the "dependency inversion principle".
The hierarchical and neat architecture of DDD realizes the decoupling of strong dependencies between layers and layers with the help of "dependency inversion principle". Let's look at the neat architecture:
From the onion view we can see:
- Dependencies are outside of the inside.
- At the core are entities and interfaces that do not depend on any other items. Next is the domain service, which relies only on entities and interfaces, and is relatively independent. They are collectively referred to as the application kernel .
- Outside the application kernel is the infrastructure layer and presentation layer, which is not necessarily dependent on each other.
Because the application kernel is not dependent on the infrastructure layer, it is easy to write unit tests.
Because the UI layer is also not directly dependent on the infrastructure layer, we can easily replace the infrastructure layer implementations (such as using a memory database) for integration testing.
Let's take a look at how eshoponweb is applying a clean architecture.
4. Project Structure
First we look at the project structure of the template schema.
From the perspective of the project structure is very simple, simple three layers, plus three test items.
Three-layer correspondence:
- Applicationcore: Domain Layer
- Infrastructure: Infrastructure Layer
- Web/webrazorpages: Presentation layer
In fact, the project architecture is the DDD classic four-tier architecture, but it integrates the application layer into the presentation layer.
4.1 Infrastructure Layer
Primarily provides common basic services and persistence.
From the code structure we can see:
- The catalog database context and generic warehousing for persistence are defined under the Data folderCatalogContextEfRepository.
- The identity database context is defined under the Identity folder.
- The logging folder defines a log adapter.
- Services defines a common mail-sending infrastructure service.
4.2. Field Level
The domain layer is the core of a project and is used to define and implement business rules. It is mainly used for entities, value objects, aggregation, warehousing, Domain Services and domain events.
From the point of view:
- The Entities folder defines three aggregate roots and related entities and value objects.
- The exceptions folder defines a common exception.
- The Interfaces folder defines a series of interfaces.
- The Services folder defines two areas of service.
- The specifications folder is the implementation of the Protocol pattern.
4.2.1. Related implementations of aggregation roots
Here we look at the relevant definitions and implementations of the aggregate root.
/// Abstract aggregate root empty interface
public interface IAggregateRoot
{}
// All entity base classes
public class BaseEntity
{
public int Id {get; set;}
}
// Shopping cart party root
public class Basket: BaseEntity, IAggregateRoot
{
public string BuyerId {get; set;}
private readonly List <BasketItem> _items = new List <BasketItem> ();
public IReadOnlyCollection <BasketItem> Items => _items.AsReadOnly ();
public void AddItem (int catalogItemId, decimal unitPrice, int quantity = 1)
{
if (! Items.Any (i => i.CatalogItemId == catalogItemId))
{
_items.Add (new BasketItem ()
{
CatalogItemId = catalogItemId,
Quantity = quantity,
UnitPrice = unitPrice
});
return;
}
var existingItem = Items.FirstOrDefault (i => i.CatalogItemId == catalogItemId);
existingItem.Quantity + = quantity;
}
}
From this implementation we can learn to:
By defining an empty interfaceIAggregateRoot, all the party roots are required to implement it.
What this does is reflected in the thought:
- Interface-Oriented Programming
- Convention is greater than configuration
- Dependency Injection
By defining oneBaseEntity, all entities are required to inherit it.
Why did you do that?
- Because an entity is characterized by a unique identity, it is implemented by defining properties in the parent classId. This is the implementation of the layer Super type .
What is the disadvantage of doing this?
Because the primary key type of all entities is not necessarily of type int, this base type is best changed to generics.
The basket aggregation root locates items as ReadOnly, in order to encapsulate the collection and prevent the child from being changed elsewhere.
4.2.2. Warehousing-related implementations
Warehousing is used for transparent persistence of domain objects.
public interface IRepository<T> where T : BaseEntity
{
T GetById(int id);
T GetSingleBySpec(ISpecification<T> spec);
IEnumerable<T> ListAll();
IEnumerable<T> List(ISpecification<T> spec);
T Add(T entity);
void Update(T entity);
void Delete(T entity);
}
public interface IAsyncRepository<T> where T : BaseEntity
{
Task<T> GetByIdAsync(int id);
Task<List<T>> ListAllAsync();
Task<List<T>> ListAsync(ISpecification<T> spec);
Task<T> AddAsync(T entity);
Task UpdateAsync(T entity);
Task DeleteAsync(T entity);
}
From the above code we can learn two points:
- Interface-Oriented Programming
- Separation of responsibilities, synchronous asynchronous interface separation.
4.2.3. Domain service-related implementations
Domain Services are used to implement business logic.
public interface IOrderService
{
Task CreateOrderAsync(int basketId, Address shippingAddress);
}
public class OrderService : IOrderService
{
private readonly IAsyncRepository<Order> _orderRepository;
private readonly IAsyncRepository<Basket> _basketRepository;
private readonly IAsyncRepository<CatalogItem> _itemRepository;
public OrderService(IAsyncRepository<Basket> basketRepository,
IAsyncRepository<CatalogItem> itemRepository,
IAsyncRepository<Order> orderRepository)
{
_orderRepository = orderRepository;
_basketRepository = basketRepository;
_itemRepository = itemRepository;
}
public async Task CreateOrderAsync(int basketId, Address shippingAddress)
{
var basket = await _basketRepository.GetByIdAsync(basketId);
Guard.Against.NullBasket(basketId, basket);
var items = new List<OrderItem>();
foreach (var item in basket.Items)
{
var catalogItem = await _itemRepository.GetByIdAsync(item.CatalogItemId);
var itemOrdered = new CatalogItemOrdered(catalogItem.Id, catalogItem.Name, catalogItem.PictureUri);
var orderItem = new OrderItem(itemOrdered, item.UnitPrice, item.Quantity);
items.Add(orderItem);
}
var order = new Order(basket.BuyerId, shippingAddress, items);
await _orderRepository.AddAsync(order);
}
From the above code we can learn:
- Dependency Injection
- Domain Services are responsible for implementing real business logic
4.3. Application layer and Presentation layer
As described above, the application layer and the presentation layer are combined in the sample project. The application layer is responsible for the coordination between the layer and the domain layer, and coordinates the business object to execute the specific application.
5. Facet-oriented programming (AOP)
AOP is also mentioned in Eshoponweb, which describes how filters are applied to AOP in ASP, such as authentication, model validation, output caching, and error handling.
5. Concise DDD
In Eshoponweb, there is a brief introduction to the concept of DDD, whether it is used, when it is used, and when not. Excerpt one or two here, of course, can also refer to my previous writing of the DDD Theory Study series.
Conclusion
- DDD is first and foremost a methodology, which focuses on the rational modeling of the domain, which is divided into strategic modeling and tactical modeling.
- If you don't know you need it, then you probably don't need it.
- If you don't know what DDD is used to solve, then you may not be experiencing these problems.
- DDD advocates also often point out that they apply only to large projects (>6 months).
Related concepts
- DDD is used to model real-world systems or processes.
- When using DDD, you need to work closely with domain experts to explain how the real system works. Identify common languages in communication with domain experts, which are used primarily to describe some concepts in the system. And the reason is Universal , because both developers and domain experts should be able to read. The concept of generic language description will form the basis of object-oriented design. The ideal state of its embodiment in code is the design of code .
Tactics
- Value objects: immutable.
- Entity: has a unique identifier variable.
- Party root: In DDD, the aggregation is an explicit grouping of the associated domain objects to express the whole concept (or a single domain object), which is used to denote the relationship between the whole and the part. For example, the domain object representing the order and the order item is combined to express the whole concept of the order in the field.
- Warehousing: A persistent pattern for isolating specific persistence measures for transparent persistence.
- Factory: Used for creation of objects.
- Services: Application services and Domain Services. Domain Services are responsible for business logic, and application services are used to express business use cases and user stories.
Strategy
- The bounding context: to provide contextual contexts for the domain, to ensure that some terminology within the domain, business-related objects (common language) has a definite meaning, there is no ambiguity.
- Context Map: The association relationship between the bounding context.
6. Application Testing
In Eshoponweb, three test items are also being tested to guide us through reasonable testing.
7. Summary
In general, the sample project is simple and easy to understand, and is primarily intended to facilitate promotion and presentation. But the knowledge points involved are not as simple as imagined, from architectural principles to design and application, each of which contains a simple knowledge system.
So what are we waiting for? Combine sample projects and official documents to build a modern WEB application using ASP. NET Core and Azure to learn, and I believe you will reap a lot.