Some thoughts on DDD in front-end applications

Author: Wu Erchang

DDD aims to solve the complexity of business logic, and business logic does not exist in the front end in most scenarios. But in some complex applications, the front-end may need to process some business logic. At this time, some ideas and methods of DDD may help organize the front-end code to make it easier to understand and maintain.

1. What is DDD

Domain-Driven Design (DDD for short) is an object-oriented software design method, its purpose is to abstract the core business domain (Domain) of the software system, and design and implement it based on this .

The core idea of ​​domain-driven design is to take the domain model as the center of software design , and improve the maintainability, scalability and reusability of the software system through the in-depth understanding and design of the domain model. A domain model is an abstract representation of a set of objects and methods that describe important concepts, entities, relationships, and operations in the business domain.

2. What problem does DDD mainly solve?

DDD aims to solve the complexity of business logic , and business logic does not exist in the front end in most scenarios . Business logic often contains a large number of business rules and constraints. These business rules are usually implemented in the backend, because the backend needs to handle data validation, processing, calculation and storage, etc.

3. Is DDD suitable for the front end?

First of all, as mentioned above, DDD mainly solves the logic problem of complex business scenarios, so a core element of whether DDD is applicable to the front end is: Does the complex core business logic exist in the front end?

I think that in most cases, complex business logic is not on the front end, which means that DDD is not suitable for front-end business in most cases .

Because business logic is high-level strategy , everything else depends on it. In addition, generally speaking, we need to ensure the stability, reliability, scalability, and maintainability of the business. If business logic is placed on the front end, it may cause data inconsistency or logic out of synchronization between multiple terminals, which may affect user experience and software reliability.

However, we cannot completely rule out the possibility of using DDD on the front end. In some complex applications, the front end may need to process some business logic, such as business form validation rules, permission control rules, etc. In this case, some ideas and methods of DDD may help organize the front-end code to make it easier to understand and maintain .

3.1 Front-ends are low-level details

As far as e-commerce system software architecture is concerned, the front end is usually regarded as a low-level detail that is relatively changeable. Therefore, when the front-end adopts a new technology stack, it is relatively easy to abandon the original technology system (for example, the business of our merchants is converted from a low-code language to Pro-Code), instead of abandoning the original back-end for a new back-end language. The factors at play here are stability and volatility.

3.1.1 What are details

The details refer to how to realize the principle, that is, the way to implement the principle, and the details are the realization of the principle. An easy way to determine whether the code you're writing is a principle or a detail is to ask yourself: Is this code enforcing the implementation of rules about my business domain, or is it just enabling something to be enforced ?

3.1.2 What is a strategy

Policy refers to what rules and principles the code we are writing should follow . Mainly deals with business logic, rules, and abstractions that exist in the domain in which we write code.

3.1.3 High-Level Strategies

High-level policy (high-level policy) usually refers to the core business logic and rules that run through various modules and components in the application. These logics and rules are the core value of the application and are usually not easily changed.

For example, in an e-commerce platform, the core high-level strategy may include how to handle the order process, how to calculate the final price of the product, how to manage inventory, and so on. These rules are implementation-independent and may require cooperation with other modules to implement.

Placing high-level policies on the backend can ensure that these rules are protected and uniformly executed, and the consistency of data and logic can be guaranteed through the interfaces and services provided by the backend. At the same time, the front end can focus on display and interaction processing, separating high-level strategies from specific implementations, making applications easier to maintain and expand.

3.1.4 Relationship Diagram of Policy and Details

A structural diagram corresponding to the front-end strategy and details is as follows:

In software architecture, we can divide it into two levels (domain and foundation)

In the domain layer we have all the important stuff: entities, business logic, rules and events. This is an irreplaceable part of our software and cannot simply be replaced with another library or framework.

In the base layer, it contains all the actual implementation for executing the domain layer code.

3.2 The front end is difficult to have stability

From the above, we can know that the domain layer has the highest level of stability and strategy . This is because the domain layer contains the domain model code that can accurately describe the business logic and operation mode of your application system. Generally speaking, the current business model is not Major changes will occur, which means that the domain layer code describing this layer of business does not need to undergo major changes, so generally speaking, the domain layer is the most stable.

According to the principle of stable dependencies , stable modules are what we can rely on. It makes sense to organize non-variable modules into a structure that depends on stable modules, but never let stable modules depend on unstable modules.

However, the reusability of the UI layer is usually poor. The front-end UI needs to be developed in a variety of design drafts, resulting in code differentiation that cannot be converged. Different user minds, design languages, business backgrounds, and business services will have a great impact on the front-end UI logic. For example, the difference in the data structure of back-end service requests and responses of different business lines may directly lead to the inability to reuse the data processing logic. This situation is particularly prominent in some C-end scenarios, such as e-commerce and social networking.

For the B-side front end, there are already some commonly used component libraries in the industry, such as Antd, Fusion, MerlionUI, etc. These component libraries already have high stability , that is, they have defined the basic component standards and basic layers of the B-side front-end, and in most cases no major changes will be made. However, due to the differences in B-side business scenarios, the front-end still needs a lot of business components and view layer workload on the UI layer. For example, in the order model of an e-commerce website, the order processing status and performance progress information centered on the buyer user is displayed for the buyer user, while the information about the order needs to be displayed for the seller user. Standard operating procedures for state transitions.

3.3 It is difficult for the front end to combine the opening and closing principles

Generally speaking, when a function needs to be changed, front-end developers usually need to directly modify the code instead of adding new functions or modules. Suppose we are developing a product details component, which may need to display the name, price, description, picture, comment and other information of the product. This information needs to be displayed for all products, so they can be defined as the core rule logic of the stable layer.

However, in different business scenarios, some customized display may be required on the product information page, such as the need to display the promotion label and atmosphere map during the promotion event, or the need to display tariffs and logistics in the cross-border e-commerce business Information, or when our product details are displayed in different countries and regions, the position of the product name and price will change, and these business rules are low-level details that are changeable, but they are often used in businesses with relatively small business volume and low-level When the rules cannot be hidden in the stable layer, this part of the workload often falls on the front-end, and finally the front-end view layer and business component layer will have a lot of business rule logic judgments. Generally speaking, this approach violates the open-closed principle, because it requires modifying existing code to implement new functions, rather than extending functional modules. This is because we put all high-level strategies on the backend and ensure that the frontend does not contain The work done at the high-level strategy.

In this case, the front-end can configure these low-level detailed rules through configuration files or the operation console, and the product information components of the stable layer can use these configurations to meet the display requirements of different business scenarios.

4. Where is the complexity of the front-end business?

The complexity of the front-end business mainly includes but is not limited to the complexity of the technology stack, the complexity of business logic, and the complexity of UI interaction .

4.1 Where is the complexity of the technology stack

Generally speaking, the front-end technology stack we refer to generally refers to: Vue, React, Angular, JQuery and other technology stacks based on MVVM and operating DOM.

Why do you say that? Because the front-end framework is essentially a high-level strategy, each front-end framework generally solves the following problems:

  • Define state (data, state)

  • State change detection (Object.defineProperty, Proxy, React reconciliation)

  • React to state changes (Observable, hooks, unidirectional data flow)

So when you choose a framework, you are actually encoding low-level details under this high-level strategy. For example, Vue and React have different strategies for implementing state change detection and Reactive. For developers, the actual coding ideas under this strategy are also very different. Vue is based on Proxy for two-way binding, while React is based on Proxy. Update the vdom tree based on the scheduled update algorithm

The programming paradigm that React brings to us is functional programming (Functional Programming is a programming paradigm whose core idea is to use pure functions for programming). When we choose the UI framework and ecology of React, we The code written naturally is based on functional style. Why functional? The reason behind it is actually because of React's native response scheme, which is to monitor changes in variable references, and then coordinate updates across the entire subtree.

Functional programming has several characteristics: pure functions, immutability, and function composition . The React response scheme is to ensure that when the input (props) is consistent, the result of the output (vdom) is also consistent. So most of our encapsulation of React state logic also needs to meet this feature, which is why we use setState() instead of state.xxx to change the state inside the component, which is what we usually call state immutability .

In addition, functional programming helps us solve the previous combination problem of components. Generally speaking, the mode of developing pages based on React is generally: Page = Compose(ComponentA + ComponentB + Fusion/Antd) and Component = Compose(Fusion/Antd + React hooks + Events + State) and this combination of features in the business layer, if there is no good component dependency principle, it will lead to serious coupling between components, and because the complexity inside the component is also a variety of compose " Components", so when the dependencies of various "components" in the system become more and more complex, or even when there is a cycle of dependencies between "components", the complexity of the business system will increase linearly.

To sum up, the complexity of the technology stack in the business front-end application is mainly reflected in the following aspects:

1) Organization of components and modules: In componentization and modular design, how to organize components and modules so that their dependencies are reasonable and clear to ensure code maintainability and scalability.

2) Organization and management of state logic: In large-scale front-end applications, state management is an important issue. State management needs to consider the consistency and variability of the state, and how to deal with state changes.

3) Asynchronous data processing: Modern front-end applications need to handle a large number of asynchronous data requests and processing. Asynchronous data processing needs to consider issues such as asynchronous data request and response, data caching, data update, and state management.

4.2 Complexity of business logic

The complexity of business logic usually comes from the business requirements themselves, such as business rules, processes, data processing, and so on. The complexity of business logic may vary depending on the business domain. For example, e-commerce, local life, live broadcast and other fields have their own business logic and complexity.

In front-end development, the complexity of business logic may be manifested as the need for a large amount of data processing, verification of business rules, complex page flow design, etc. In the front-end, if there is no clear business logic division and abstraction, the code may become very complex and difficult to maintain and expand. Therefore, in front-end development, the division and abstraction of business logic is very important, so as to better deal with the changes and complexity of business logic.

4.3 The complexity of UI interaction

The complexity of UI interaction mainly lies in how to implement complex interaction logic and animation effects, and how to handle user input and feedback. Specifically, the complexity of UI interaction is mainly reflected in: user experience design, cross-terminal compatibility, and performance optimization.

5. How to reduce the complexity of front-end business

Here we mainly focus on how to reduce the complexity brought about by the complexity of technology stack and business logic.

At the beginning of the article, we said that the main purpose of domain-driven design is to solve the complexity of business logic. The core idea of ​​domain-driven design is to take the domain model as the center of our business architecture design . In fact, we just need to use some ideas of domain-driven design to practice in front-end business development. As mentioned earlier, there will be a lot of front-end business projects in our business. The principle of non-dependency ring of component construction is not satisfied. The main reason here is that front-end developers are developing with the UI layer as the center , and if we switch perspectives to build our front-end business with ViewModel and Model as the center, the overall system design Mindsets change.

5.1 Domain Model

Most of the front-end code has nothing to do with the actual business domain. This part of the front-end mainly focuses on form verification, API request, event response, list rendering, etc. However, some business front-ends are indeed related to the business domain, and the domain business model is also true. Will affect the UI layer. The domain model is usually a set of classes or objects with business meaning, which encapsulate the key business logic of the system through methods, properties, etc. In any case, as long as it can be reused by other different applications in the system.

Generally speaking, we can construct model data through manual creation or abstract factory methods, map these data to the view layer responsively, and then call functions or methods in the model layer according to events triggered by the view layer to update the model layer data.

5.2 State Management

State management is mainly divided into two parts: one is to manage the mutable state and immutable state of the business model layer, and the other is to manage the mutable state and immutable state of the view layer.

Some states on the view layer do not come from the model layer data, but are pure page states, such as the sign that the data is loading, the linkage of the drop-down box, etc., which have nothing to do with the model layer and change dynamically as the requirements change .

In the React-based rendering solution, we can use React’s native response solution or the third-party library (mobx) solution to achieve this part of state management. Different options may bring differences in programming paradigms (FP, OOP) ).

5.3 View layer

The view layer is the most unstable layer. The implementation of UI components is usually affected by business requirements. As the requirements change, the implementation of UI components also needs to be adjusted and changed accordingly. At the same time, in order to maintain the stability and maintainability of the software, it is necessary to follow the principle of stable dependence to ensure that other basic components do not depend on the view layer.

The React-based view layer will have side effects such as event response and life cycle. Hooks are actually just a thing of the view layer, and they all rely on the response principle of React. Therefore, in my opinion, Hooks will enter the view layer by merging similar items.

5.4 Architecture layering

First of all, in the Internet industry, it is difficult to complete a perfect system design from the beginning. On the contrary, the system often needs to be developed gradually, gradually taking shape through continuous iteration and introduction of new functional modules. For existing systems, it is difficult to solve all problems through a large-scale refactoring. A good system design requires continuous work and gradual accumulation of details to achieve a complete system. Therefore, it is necessary to attach great importance to design and detail improvement in daily work.

Second, specialized division of labor and code reuse are important means to improve software productivity. In addition, services in the same domain can support different upper-layer application logic. The idea behind this division of labor and reuse is to divide the system into multiple horizontal layers and clearly define the roles and tasks of each layer to reduce the complexity of individual layers. At the same time, each layer only needs to provide a consistent interface to adjacent layers, which can be implemented in different ways, which provides support for software reuse. Therefore, layering is an important principle for solving complexity problems.

5.4.1 Option 1

In this solution, the interface between our view layer and the model layer is managed through the ViewModel. The view layer relies on Hooks and ViewModel for life cycle, event, and response solutions, and the ViewModel includes both the state management of the current view layer itself. It also couples domain services and domain models. The unstable layer in this architecture design includes: View, Hooks, Lifecycle, and ViewModel, while the stable layer includes: Service, Repository, and Model.

5.4.2 Scheme 2

In this solution, there is a direct dependency relationship between our view layer and the model layer, and the view layer directly depends on Hooks, Model, and State. The unstable layer in this architecture design includes: View, Hooks, Lifecycle, State, Model, while the stable layer includes: Service, Repository.

5.4.3 File directory design

According to the hierarchical idea of ​​the above structure diagram, we have defined the following file directories in the actual project:

├── shared
│   ├── components // 公用基础组件, 组件之间不能互相耦合
│   ├── constants // 全局变量
│   │   ├── page.ts
│   ├── domains // 领域层
│   │   ├── page
│   │   │   ├── page.model.ts // 实体
│   │   │   └── page.service.ts // 领域Service服务
│   │   ├── ...
│   └── util // 公用函数
│       └── http.ts
├── components // 公共业务组件,业务组件之间不能互相耦合,但是可以依赖公共基础组件
├── modules // 模块视图,模块可以是compose(公共业务组件, 公共基础组件)
└── page // 页面视图层
    ├── index
    │   ├── index.tsx
    │   ├── components
    ├── ...

common problem

Question 1: The distinction between Modules and Components is actually too idealistic. In normal business development, it may not be easy to judge whether my current component should be placed in Modules or Components, and even users will wonder whether I should go to Modules to find it or not. Go to Components to find it.

Module = compose(ComponentA + ComponentB + ComponentC). If ModuleA is just a special ComponentA, put it in the component. There is not too much business logic coupled in the module, and it is pure compose of the View layer. It's just that this module needs to be referenced by multiple pages, for example: PageA = compose(ModuleA + ComponentD + PageA logic ) PageB = compose(ModuleA + ComponentC + ComponentB + Hooks ).

Question 2: How fine is the granularity of your Components? Can the Components in my Components refer to other Components?

No, separation of concerns and layered architecture is to make the dependency tree clear enough. Components can depend on shared/components or Fusion + MerlionUI, but it is best not to couple components with each other.

When choosing a specific solution, you need to consider the differences in business scenarios. For simple business attributes, you must be wary of complicating the problem and over-designing. For complex businesses, you must comprehensively evaluate and judge the solution, and choose the design solution that suits you.

In the other two design schemes, the unstable layer does not mean that this is a strong coupling layer. Instability just means that the structure in this layer will change frequently as the business changes. We need to judge which parts need to be based on the business scenario. Transform into a stable layer and ensure a clean and tidy dependency structure.

5.5 Summary

According to the above derivation process, we can know that if we want to deeply apply the idea of ​​domain-driven design in front-end business engineering to practice, it is best to need several prerequisites:

  • Premise 1: Developers need to design the system from the perspective of the domain model layer as the center.

  • Premise 2: Developers need to have a sufficient understanding of the business domain model, and the front-end and back-end business domain models must be aligned.

  • Premise 3: The backend can provide a standardized CRUD interface in the business domain.

Written at the end: Although technology plays an important role in software development, any technology is not a silver bullet. As engineers and architects, we need to think carefully about technology selection, architecture design, and system design, and conduct a comprehensive evaluation and judgment.

Guess you like

Origin blog.csdn.net/AlibabaTech1024/article/details/130821094