W27 - Domain-Driven & Front-Back Interaction Patterns
Some reflections on Domain-Driven Design
How to keep the core of our project stable amid rapid change, so complexity doesn’t spiral out of control and force a refactor in later iterations.We are already facing the problem of business knowledge being lost as it spreads through the team. As the team grows and personnel change, this issue will only worsen. Sothe core is the understanding and abstraction of the business; mapped into software design, I believe this means domain-based design.
Compared with the front end, the back end typically does more detailed and far-reaching design. Projects need to be built on a deep understanding of the business — the deeper the understanding and the more complete the abstraction, the better the maintainability and extensibility. To some extent, without backend design, you might not even be able to start.
The front end is different: our technical solutions are more about pure technical points and implementing business flows. We still don’t do enough deep business breakdown. To be honest,our solution design basically still stays at page-oriented implementations. Rarely do we see abstractions of the business that lead to designing components and software structural layers.
In a good backend project, the table structures and service layer are relatively stable, thanks to deep understanding and abstraction of the business domain. The merchant side is currently in a phase of rapid business iteration, and the UI and interactions will change quickly as a result. How can we, as much as possible, use the unchanging to respond to changing circumstances? How can we support business growth at lower overall cost? I want to find the answer in Domain-Driven Design.
Take the checkout as an example. This refactor of version i organizes the application around container components and presentational components. Container components handle state management and business logic; presentational components are pure components responsible only for mapping data to views. The checkout frontend’s business logic isn’t that complex, yet the container component is already over 600 lines. It’s still fairly clean now, but soon loan payments, wealth-management payments, and marketing features will be added and the container will grow thicker. What to do? We must split it. How to split? By domain. The core of the checkout frontend is handling payment methods; we should perform domain abstractions for payment methods with different business attributes. Each payment method corresponds to a different asset provider, so it can be treated as a domain. Different domains have their own attributes and behaviors. For example, quick-pay has attributes like a card list and behaviors like selecting a card to enter a PIN. Loan payment has attributes such as installment terms and amounts, behaviors like doing facial-recognition for loans, and behaviors like retrieving loan contracts. These are the underlying business logic and are relatively stable. No matter how the UI changes in the future, the project core — the domain layer — won’t change. Even migrating the frontend stack from Vue to React can preserve the core stably.
I have been weaker in design before; my ideas about Domain-Driven Design need further discussion and collision to evolve.
Some insights on frontend-backend data interaction patterns
Compared with wallet business, payment business shows clear design differences in frontend-backend interface interaction. In payment scenarios like checkout, card binding, and bill payment, the APIs provided to the frontend are basically page-oriented. The relationships between interfaces are designed entirely according to the human-computer interaction sequence: an earlier interface returns the URL of the next interface, request parameters, and everything the current page needs — almost enough to render the next page directly.
The advantage of this approach is that the frontend’s responsibilities are concentrated on human-computer interaction, which looks simple. The backend can have relatively flexible control over interface interactions, freely deciding subsequent interfaces, calling different downstream services, and ultimately returning data, avoiding the need to negotiate with the frontend.
The downside is also obvious: this pattern tightly couples API and view — or the human-computer interaction flow — making it fragile in terms of extensibility. This collaboration model works because the frontend view and business flow are stable enough. In fact, payment business does meet this precondition reasonably well. Although this pattern is less universal, the fact that payment architecture was designed this way implies deeper considerations. At present, in payment scenarios I can’t say it’s absolutely good or bad; it needs to be experienced in actual work.
In the quick-pay project, Interface A issues the next request interface and determines subsequent interfaces. The backend needs to be aware of frontend versions, either through a version number or via full-chain canarying keyed by customer ID.
Last updated