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 people change, the problem will only get worse. Sowhat's core is the understanding and abstraction of the business, which, mapped to software design, I believe is domain-based design.
Compared with the frontend, the backend often makes more detailed and far-reaching design decisions. Projects need to be built on a deep understanding of the business: the deeper the understanding and the more thorough the abstraction, the better the project's maintainability and extensibility. To some extent, the backend can't even start without proper design.
The frontend is different. Our technical solutions are more reflected in pure technical points and the implementation of business workflows. We still don't do enough deep decomposition of the business. To be honest,our solution design still basically remains oriented toward page implementation. It's rare to see abstractions of the business that then inform component and software architecture design.
In a good backend project, the database schema and service layer are relatively stable, thanks to a deep understanding and abstraction of the business domain. The merchant side is currently in a phase of rapid business iteration, and accordingly the UI and interactions will change quickly. How can we maximize “responding to change with the unchanging”? How can we support business growth at lower overall cost? I want to find answers in Domain-Driven Design.
Take the checkout page 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 particularly complex, but the container component still reached over 600 lines. It looks relatively clean now, but soon loan payments, investment balance payments, and marketing features will be added, and the container component will grow thicker. What to do? You must split it. How? Split by domain. The core of the checkout frontend is handling payment methods; payment methods with different business attributes should be abstracted as domains. Each payment method corresponds to different asset providers, so it can be understood as a domain. Different domains have their own specific 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 like installment terms and amounts, behaviors like performing facial-recognition for loans, and obtaining loan contracts. These are the low-level business logic and are relatively stable. No matter how the UI changes, the project's core — the domain layer — won't change. Even if the frontend tech stack migrates from Vue to React, the core can be migrated stably.
I've been weak on design in the past; my ideas about Domain-Driven Design still need further discussion and collision to evolve.
Some insights on frontend-backend data interaction patterns
Compared with the wallet business, the payment side shows clear design differences in frontend-backend interface interactions. In payment businesses like checkout, card binding, and bill payment, the APIs provided to the frontend are basically designed for pages. The relationships between interfaces are designed exactly according to the sequence of human-computer interaction: an earlier interface returns the URL of the next interface, request parameters, and everything the current page needs — almost ready to render the next page directly.
The benefit of this approach is that frontend responsibilities concentrate on human-computer interaction, which seems simple and focused. On the backend, interface interactions can be controlled flexibly; the backend can freely decide subsequent interfaces, call different downstream services, and ultimately return data, avoiding back-and-forth coordination with the frontend.
The downsides are also obvious: this pattern tightly couples APIs with views, or with the human-computer interaction flow, making it fragile in terms of extensibility. This cooperation model works because the frontend views and business flows are stable enough. In fact, payment business does fit that precondition reasonably well. Although this pattern is less general, there were likely deep considerations when it was chosen for payments. At present, in payment scenarios I can't say it's absolutely good or bad — it needs to be experienced in practice.
In the quick-pay project, API A issues the next request interface and determines subsequent interfaces. This requires the backend to be aware of the frontend version, either via version numbers or via full-link gray release controlled by customer IDs.
Last updated