11.11 Big Sale for Cloud. Get unbeatable offers with up to 90% off on cloud servers and up to $300 rebate for all products! Click here to learn more.
Everyone who follows my work knows that I have been committed to the governance of application architecture and code complexity. Recently, I have been studying the code of the Ling Shou Tong product domain. The complex business scenario of Ling Shou Tong poses a new challenge at the architecture and code levels. To address the challenge, I have conducted a carefully thought out study. On the basis of the actual business scenario, I have developed a set of methodologies on how to code complex applications, and today, I would like to share these methodologies with you.
Let's begin with a brief background about Ling Shou Tong. It is a B2B model for offline stores that is developed to reconstruct traditional supply chain channels through digitization for improving supply chain efficiency and boosting New Retail. In this process, Alibaba acts as the platform that provides the service functions of Bsbc.
Firstly, in the product domain, a "launch" action is performed. Once, the product is launched, it can be sold to various mom-and-pop stores through Ling Shou Tong. Launching a product is one of the key business operations in Ling Shou Tong. Therefore, it involves several verification and association operations. A simplified business process for product launching is illustrated below:
For addressing a complex business scenario, writing code by using a service method is not feasible. So, if there is no way to address it by using one class, it is recommended to use decomposition instead.
Actually, if engineers can recall the "divide and conquer" method, it is regarded as a good job. At least, it is better to have the consideration for decomposition than none. I have also encountered business scenarios of similar complexity, which are processed by using a number of methods and classes without decomposition.
However, there is a challenge with decomposition as well. Many engineers rely too much on tools or auxiliary means to implement decomposition. For example, in our product domain, we have a minimum of three similar decomposition methods, such as self-made process engines and database-based process handling.
To put it simply, all these methods are only auxiliary to the pipeline processing and do not add anything considerable. Therefore, I recommend that we follow the Keep It Simple and Stupid (KISS) approach by not using any tools with a simple pipeline mode as the suboptimal choice and methods such as process engines as the last choice. Unless your application has a strong demand for process visualization and orchestration, it is recommended not to use any tools such as process engines. This is suggested, primarily because it introduces additional complexity, especially when the process engines require persistence, and secondly, as it splits the code, which results in poor readability of the code. To be bold, it is estimated that 80% of the use of process engines is not worthwhile.
Coming back to the main topic of product launching, there are few essential questions that need to be addressed:
Apparently, answers to both these questions is a clear "no". The core point should be how to break down the problem and abstract it. If you know the pyramid principle, you can use structured decomposition to deconstruct the problem into a hierarchical pyramid structure as shown below:
The code written as per the decomposition method is like a book with clear directories and content. Taking the example of product launching, the program entry is an OnSale command that consists of three phases.
@Command
public class OnSaleNormalItemCmdExe {
@Resource
private OnSaleContextInitPhase onSaleContextInitPhase;
@Resource
private OnSaleDataCheckPhase onSaleDataCheckPhase;
@Resource
private OnSaleProcessPhase onSaleProcessPhase;
@Override
public Response execute(OnSaleNormalItemCmd cmd) {
OnSaleContext onSaleContext = init(cmd);
checkData(onSaleContext);
process(onSaleContext);
return Response.buildSuccess();
}
private OnSaleContext init(OnSaleNormalItemCmd cmd) {
return onSaleContextInitPhase.init(cmd);
}
private void checkData(OnSaleContext onSaleContext) {
onSaleDataCheckPhase.check(onSaleContext);
}
private void process(OnSaleContext onSaleContext) {
onSaleProcessPhase.process(onSaleContext);
}
}
Each of these phases can be split into multiple steps. Using the OnSaleProcessPhase
as an example, it contains a series of steps as mentioned below:
@Phase
public class OnSaleProcessPhase {
@Resource
private PublishOfferStep publishOfferStep;
@Resource
private BackOfferBindStep backOfferBindStep;
//omit other steps
public void process(OnSaleContext onSaleContext){
SupplierItem supplierItem = onSaleContext.getSupplierItem();
// generate OfferGroupNo
generateOfferGroupNo(supplierItem);
// publish offer
publishOffer(supplierItem);
// bind back offer stock
bindBackOfferStock(supplierItem);
// synchroize sotck
syncStockRoute(supplierItem);
// set virtual product tag
setVirtualProductExtension(supplierItem);
// set procteciton label
markSendProtection(supplierItem);
// record Change Details
recordChangeDetail(supplierItem);
// synchronize price
syncSupplyPriceToBackOffer(supplierItem);
// set exteinsion info
setCombineProductExtension(supplierItem);
// remove sellout tag
removeSellOutTag(offerId);
// fire domian event
fireDomainEvent(supplierItem);
// close to-do issues
closeIssues(supplierItem);
}
}
In this process of complex product launching scenario, it is crucial to answer the following two questions:
The answer to both the questions is "No", and hence the simple composed method cannot be more applicable to express such a business process.
Therefore, while implementing process decomposition, it is suggested that engineers should not focus too much on tools or the flexibility brought about by design modes. Instead, we should spend more time on problem analysis, structural decomposition, and reasonable abstraction to finally obtain appropriate phases and steps.
The code after process decomposition is clearer and easier to maintain than before. However, it is important to note the following two problems associated with decomposition:
For example, a verification is performed to check the inventory during the product launching process. The inventory processing of combined products (CombineBackOffer) is different from that of ordinary products. The original code mentioned below:
boolean isCombineProduct = supplierItem.getSign().isCombProductQuote();
// supplier.usc warehouse needn't check
if (WarehouseTypeEnum.isAliWarehouse(supplierItem.getWarehouseType())) {
// quote warehosue check
if (CollectionUtil.isEmpty(supplierItem.getWarehouseIdList()) && !isCombineProduct) {
throw ExceptionFactory.makeFault(ServiceExceptionCode.SYSTEM_ERROR, "You cant publish offer, since there is no warehouse info");
}
// inventory amount check
Long sellableAmount = 0L;
if (!isCombineProduct) {
sellableAmount = normalBiz.acquireSellableAmount(supplierItem.getBackOfferId(), supplierItem.getWarehouseIdList());
} else {
//combination product
OfferModel backOffer = backOfferQueryService.getBackOffer(supplierItem.getBackOfferId());
if (backOffer != null) {
sellableAmount = backOffer.getOffer().getTradeModel().getTradeCondition().getAmountOnSale();
}
}
if (sellableAmount < 1) {
throw ExceptionFactory.makeFault(ServiceExceptionCode.SYSTEM_ERROR, "Your stock is less than 1, please supply more items. The product id:" + supplierItem.getId() + "]");
}
}
However, if we introduce the domain model in the system, the code will be simplified as follows:
if(backOffer.isCloudWarehouse()){
return;
}
if (backOffer.isNonInWarehouse()){
throw new BizException("You cant publish offer, since there is no warehouse info");
}
if (backOffer.getStockAmount() < 1){
throw new BizException("Your stock is less than 1, please supply more items,The product id:" + backOffer.getSupplierItem().getCspuCode() + "]");
}
Obviously, the expression after using a model is much clearer and easier to understand. In addition, you do not need to make judgments on whether they are combined products or not. Due to a more realistic object model (CombineBackOffer inherits BackOffer) adopted in the system, we can eliminate most of the if-else statements in our code through object polymorphism.
From the preceding case, we can infer that using process decomposition is better than none. Furthermore, process decomposition plus object models are better than process decomposition alone. In the case of product launching, if we adopt process decomposition plus object models, we will get the following system structure:
In the preceding sections, we covered how to code complex applications. To be precise, it is the combination of top-down structured decomposition and bottom-up object-oriented analysis. Now, let us further abstract the preceding case to form a feasible methodology that can be used in more complex business scenarios.
The top-down and bottom-up combination suggests combining top-down process decomposition and bottom-up object modeling to spirally build our application system. This is a dynamic process. The two operations can be carried out alternately or simultaneously. Moreover, they complement each other. The upper layer analysis can help us better clarify the relationship between models, while the lower layer model expression can improve our code reusability and business semantic expression capabilities.
The following figure shows the process:
This combination helps us to write clean and easy-to-maintain code for any complex business scenarios.
While using the domain-driven design (DDD) in practice, we experience the following two phases:
With reference to my approach, I would define myself near the second phase due to the questions that have been perplexing me: What capabilities should be placed on the Domain layer, and is it reasonable to follow the tradition to collect all services to the Domain layer? To be honest, I have never found answers to these questions.
In real business scenarios, many capabilities are specific to use cases. If you use the Domain layer to collect services blindly, it is likely that little benefit is obtained. On the contrary, the collection will lead to the expansion of the Domain layer, which will affect reusability and expression capability.
In this view, I think that we should adopt the strategy of capability sink-in. It implies that we do not force ourselves to design all capabilities of the Domain layer at one time, and we do not have to place all business capabilities on the Domain layer. Instead, we must adopt a pragmatic attitude, for abstracting and extracting only the capabilities that need to be reused in multiple scenarios and temporarily put the capabilities that are not reused in the use cases at the App layer.
Note: Use case is a term used in the book Clean Architecture. To express this term in a simple manner, it is the process of responding to a request.
Through practice, I have found that this step-by-step capability sink-in strategy is a more practical and agile method, as we agree that the model is not designed at one time, and is a result of iterations.
The sink-in process is shown in the following figure. If we find that step 3 of use case 1 and step 1 of use case 2 have similar capabilities, we can consider extracting and migrating the capabilities to the Domain layer. In this way, code reusability is improved.
Reusability is about determining when the sink-in should be performed or to put it simply, when the code is repeated. Cohesion is about ascertaining how the sink-in should be performed, in other words, whether a capability is cohesive to an appropriate entity, and whether it is placed on the appropriate layer.
The Domain layer has two levels of capabilities: One is domain service, which is relatively coarse-grained, and the other is the domain model, which is the most fine-grained reuse. For example, in our product domain, a capability is often required to determine whether a product is the smallest unit or a middle package. It is necessary that such a capability should be directly cohesive to a domain model.
public class CSPU {
private String code;
private String baseCode;
//omit other attributes
/**
* check if it is minimumu unit
*
*/
public boolean isMinimumUnit(){
return StringUtils.equals(code, baseCode);
}
/**
* check if it is middle package
*
*/
public boolean isMidPackage(){
return StringUtils.equals(code, midPackageCode);
}
}
Traditional models didn't have any domain model or CSPU entity. As a result, you can find that the logic for determining whether a single product is the smallest unit is scattered in the code in the form of StringUtils.equals (code, baseCode)
. Such code always has poor intelligibility and it is difficult to infer what it means at first sight.
Here, I would like to answer the questions that have confused many peers who are engaged in application development.
From the preceding case, we can comprehend that the complexity of application development is no less than framework development. It is not easy to code applications. The only difference between application and framework development personnel is that they are dealing with different problem domains.
While application development involves more domain changes and more people, framework development involves more stable problem domains but more sophisticated technologies. For example, if you want to develop Pandora, you must have a deep understanding of the Classloader.
However, all the application and framework development personnel share certain thinking patterns and abilities. For example, the ability to break down problems, abstract thinking, and structured thinking.
In my opinion, if a developer cannot do well in application development, he or she cannot do well in framework development either, and vice versa. Application development is not simple at all. However, many of us have treated it in a simple manner.
In addition, from the perspective of changes, the difficulty of application development is not inferior to that of framework development, and application development faces even greater challenges. Therefore, as closing thoughts, I would like to suggest to all peers engaged in application development, to:
This article is a summary of my recent thoughts and is based on some knowledge of DDD and application architecture. If you are not thorough with the domain knowledge, some parts may appear abrupt or you might not comprehend what I am trying to convey in the article
If time permits, you can read the books, Domain-Driven Design and Clean Architecture to get some preliminary knowledge.
3 posts | 0 followers
FollowAlibaba Cloud Serverless - June 9, 2022
Alibaba Clouder - November 23, 2020
Alibaba Cloud Serverless - August 4, 2021
Alibaba Clouder - May 10, 2021
Alibaba Clouder - April 15, 2021
Alibaba Developer - January 10, 2020
3 posts | 0 followers
FollowGet started on cloud with $1. Start your cloud innovation journey here and now.
Learn MoreTransform your business into a customer-centric brand while keeping marketing campaigns cost effective.
Learn MoreThis built-in AI solution allows Financial Institutions (FIs) to dynamically develop, train, and deploy credit risk and fraud risk models to decide digital lending applications in real-time and control risks.
Learn More