I hope everyone is safe in the epidemic and the company will still be there when I come back.


SKR Shop is a group of low-level code farmers. Due to the mental disorder tortured by the project at work, and due to the pride of programmers: the system designed by others is shit, my design is the best in the universe, so I decided to make a design without coding e-commerce design manual.

Project address: https://github.com/skr-shop/manuals

The requirements analysis for shopping cart design in the previous article described the general requirements for shopping carts. This paper focuses on how to implement the architecture design (business + system architecture).

instructions

Architectural design can be divided into three levels:

  • Business architecture
  • System architecture
  • The technical architecture

Quickly and simply explain the meaning of the following three architectures; When we got the shopping cart requirement, we said Golang for implementation, Redis for storage; This describes the technical architecture; We do code layering for the shopping cart code project, design specifications, and planning of dependency systems called system architecture;

What is the business architecture? Business architecture is essentially a literal description of system architecture. What do you mean? When we get a demand, we should first communicate with the demand side to establish a unified cognition. For example, normative nouns (items in the shopping cart have different meanings than items in the merchandise system); Create a model that everyone can understand, the interaction between the shopping cart, the user, the item, and the order, and what functions each has.

There are many methodologies in business architecture analysis, such as Domain-driven design, but it is not the only or the best approach to business architecture analysis. Whatever is right for you is best. Entity relationship diagrams and UML diagrams are also business architecture domains.

The strong point here is that no matter how you model your design, any design is better than no design at all, and the second is to make sure you model it into your code.

The analysis of business architecture in this paper uses DDD (domain Driven design). Or is that the word fit is the best.

Business architecture

From the previous requirements analysis, we have identified what our shopping cart is going to do. Let’s take a look at a typical user handling a shopping cart.

The user journey

In this process, the user uses the carrier shopping cart to complete the purchase process of goods; The continuously flowing data is the commodity, and the carrier of shopping cart is stable. This is the point of stability and the point of change in our system.

Goods may flow in various ways, such as from different places to the shopping cart, different ways to add to the shopping cart, the life cycle is different in the shopping cart; However, the process is stable and must have items in the cart before clearing and ordering.

The life cycle of an item in a shopping cart is as follows:

process

Following this process, let’s look at the corresponding operations for each stage.

The operation corresponding to the procedure

One thing to note here is that this operation before adding a car can actually be put into the add operation of the cart, but this part is very unstable and changeable. We kept it separate so that we could expand it later without affecting the relatively stable shopping cart stage.

These three phases, which should be called entities according to DDD concepts, collectively constitute the shopping cart domain; We’re not going to talk about these concepts today, but we’re going to skip them, and we’ll have a chance to do a separate post later.

Are extra trains to the former

Through process analysis, we summed up the operation interface that the system needs to have, as well as the entity corresponding to these interfaces. Now we first look at what we should do before adding cars;

In fact, before adding the car is mainly to prepare to add shopping cart goods for each latitude of the check, check whether to meet the requirements.

Before letting users add cars, we first solve the problem of where users sell from, and then verify? Because the same product is purchased from different channels, there are different situations. For example, for Xiaomi mobile phone, whether we buy it through seconds kill, crowdfunding through friends, or directly in the mall, the price is different, but it is actually the same product.

The second question is whether you have the qualification to purchase, or as mentioned above, not everyone can add the operation of second kill and crowd funding, so you have the existing qualification. So the qualification check is also here;

The third question is to verify the commodity attributes of the purchased goods, such as whether they are on or off shelves, have stock, limit the purchase quantity and so on.

And as you can see, the validation conditions here can be very variable. How do you build code that’s easy to extend?

Verification of additional vehicles

Throughout the carpooling process, it is important to distinguish between different validations based on source. We have two options.

Method 1: through the strategy mode + facade mode to get it done. Strategy is to carry out different verification according to different sources, facade is to encapsulate a strategy according to different sources;

Method 2: Through the responsibility chain mode, but there needs to be a change. During the execution of this chain, some nodes can be skipped. For example, seckill does not need the verification of inventory or crowdfunding.

Through comprehensive analysis, I chose the mode of responsibility chain. Post the core code

B. To bounce lightly over (b. To be Skipped). To bounce lightly over (b. To be Skipped). To bounce lightly over (B. To be Skipped) All sorts of validation is done here}

Type RequestChain struct {Handler Next *RequestChain}

Copy the code

Func (h *RequestChain) SetNextHandler(in *RequestChain) *RequestChain {h.ext = in return in}

About design patterns, you can see my friend github:https://github.com/TIGERB/easy-tips/tree/master/go/src/patterns

The shopping cart

Before we add cars, let’s look at the shopping cart. As we discussed earlier, shopping carts can come in a variety of forms, such as storing multiple items at a time and paying for one item at a time. Therefore, the shopping cart must choose the shopping cart type according to the channel.

This part of the operation is relatively stable. Let’s just pick a few of the more important operations and talk about the idea.

Add to shopping cart

By preloading the conditional validation, you can see that this part of the logic has become very lightweight when it comes to overloading operations. The main thing to do is the logic of the following parts.

Add to shopping cart

There are a few tricks here. The first one is the logic to get the goods, because it will be used in the previous verification, so here after getting the goods will be passed in the way of parameters, so there is no need to read the library or call the service to get;

Second, we need to get the current user’s existing shopping cart data and then add the added item to it. This is a kind of merge operation, originally the goods exist, equivalent to the quantity plus one; It is necessary to pay attention to whether this product has a father-son relationship with the existing products, and whether it may change some activity rules after joining, for example: originally, if you buy 2 products, you can give 1 gift, but now if you add one more product, you can give 2 gifts;

Note: this does not change the quantity in the shopping cart, but in the list or details page.

The merged shopping cart data is written directly back to storage after it is confirmed to be ok by the marketing campaign.

Combined shopping cart

Why is there a merge shopping cart operation? Because the general e-commerce is allowed to operate as tourists, so when users log in, they need to merge the two.

The logic of merging many parts here is logic that can be reused with adding to the shopping cart. For example, the merged data needs to be checked for validity and then overwritten back to the storage. So you can see the correlation here. The design method is to some extent generic.

Shopping cart list

Shopping cart list this is a very important interface. In principle, there are two types of shopping cart interface: a simple version and a full version;

The simple version of the list interface is mainly used in the upper right corner of the PC home page to get simple information; The full version is used in the shopping cart list.

In practice, the shopping cart is more than just a read interface. Because we all know that both commodity information and activity information are constantly changing. Therefore, each reading interface must check the validity of the data in the current shopping cart, and then overwrite the data stored in the original store if inconsistency is found.

Shopping cart list

There are also some practices that check the validity of the data on each interface. I suggest that for performance reasons, some interfaces can relax the check and complete the check when fetching the list. For example, when I add an interface, I only check the validity of the items I add, not the entire cart. Since this operation is usually followed by a list operation, validation is also performed, and both operations repeat, so only the latter is taken.

settlement

Settlement consists of two parts, settlement page details and submit the order. The settlement page is a wrapper around the shopping cart list, because the biggest difference between the settlement page and the list page is that it requires the user to select a shipping address (virtual goods are another word), which produces more explicit price information, but otherwise is basically the same. Therefore, when designing the shopping cart list interface, we must take full consideration of universality.

Another thing to note here is that the purchase now is also done through the billing page interface, but internally the add interface is still called to add the item to the cart; There are three important points to note. First, the add operation is done inside the service, so the service caller does not need to be aware of the existence of the add operation. Secondly, the Key of the shopping cart in Redis is independent from the ordinary shopping cart, otherwise it is very difficult to operate and deal with the goods coupling between the two. Finally, the shopping cart to be purchased immediately should consider that the data of each account should not affect each other when logging in from multiple terminals. Here, the UUID of each terminal can be used as the mark of the shopping cart to avoid this situation.

The last step of the shopping cart is to generate the order, and the most important thing in this step is to lock the shopping cart to avoid data tampering in the submission process. Add one more sentence, many people write Redis distributed lock code has defects, we must pay attention to the problem of atomicity, this kind of article on the network will not be repeated.

After the success of locking, we have a variety of methods here. One is to start to write tables according to the organization data involved in DB, which is suitable for the business volume requirements, such as the order volume per second does not exceed 2000K; What if your system has very high concurrency requirements?

In fact, it is very simple, one of the three magic weapons of high performance: asynchronous; We write snapshots of the data directly to MQ at commit time and consume them asynchronously, which can be increased by controlling the number of consumers. Although this method improves performance, it also increases complexity. You need to choose according to your actual situation.

Now that I’ve finished with the business architecture design, let’s look at the system architecture.

System architecture

The system structure mainly includes how to map the service architecture and the description of the corresponding input parameters and output parameters. Since the input and output are determined by their respective businesses and there is no difficulty, we will only talk about how to map the business architecture to the system architecture, as well as the core Redis data structure selection and stored data structure design in the system architecture.

The code structure

The following code directory is designed with Golang in mind. Let’s look at how the business architecture above can be mapped to the code level.

├ ─ ─ addproducts.go

├ ─ ─ cartlist.go

├ ─ ─ mergecart.go

├ ─ ─ the entity

│ ├ ─ ─ cart

│ │ ├ ─ ─ the add.go

│ │ ├ ─ ─ cart.go

│ │ └ ─ ─ the list.go

│ ├ ─ ─ the order

│ │ ├ ─ ─ checkout.go

│ │ ├ ─ ─ the order.go

│ │ └ ─ ─ submit.go

│ └ ─ ─ precart

├ ─ ─ the event

│ └ ─ ─ sendorder.go

├ ─ ─ the facade

│ ├ ─ ─ the activity.go

│ └ ─ ─ the product.go

└ ─ ─ repo

Copy the code

There are four directories on the outer layer, entity, Event, Facade, and repo. Their responsibilities are as follows:

Entity: Stores the three entities in the shopping domain we analyzed earlier; All major operations are on these three entities;

Event: This is used to handle the generated events, for example, if we are submitting orders asynchronously, then this directory is responsible for sending the data to MQ;

Facade: What does the directory do here? This is mainly because our services also depend on services such as goods and marketing campaigns, so we should not call it directly in the entity, because the third party may change, or increase or decrease, we do the following simple encapsulation (facade pattern in design pattern);

Repo: This directory can be understood to some extent as the Model layer, and if you are dealing with persistence in the entire domain service, it is done through it.

The last few outer files are the domain services we provide for the application layer to invoke.

In order to ensure the compactness of the content, I give up the introduction of the whole catalog of micro-services here, and only introduce the domain services separately. I will introduce the whole system architecture of micro-services separately in the future.

By dividing above, we accomplish two things:

  1. The structure of the business architecture analysis is mapped in the system code, and they are reflected in each other. The biggest benefit of this is that the design is consistent with the code, so you can read the documentation and know where the corresponding code is;

  2. Each directory has separate concerns, making it more cohesive and easier to develop and maintain.

Redis store

Now, we choose Redis as the storage of shopping commodity data. We need to solve two problems. First, what data do we need to store? Second, what structure do we use to store?

Many online shopping cart are only save a commodity ID, the real scene is difficult to meet the needs. Think about it. How does a product ID remember a user’s choice of freebies? The last activity the user selected? And the channels of purchase?

I give a reference structure for the general scenarios:

// Shopping cart data

type ShoppingData struct {

 Item       []*Item `json:"item"`

 UpdateTime int64   `json:"update_time"`

 Version    int32   `json:"version"`

}



// Single item element

type Item struct {

 ItemId       string          `json:"item_id"`

 ParentItemId string          `json:"parent_item_id,omitempty"` // The parent item ID of the binding

 OrderId      string          `json:"order_id,omitempty"`       // Bind the order number

 Sku          int64           `json:"sku"`

 Spu          int64           `json:"spu"`

 Channel      string          `json:"channel"`

 Num          int32           `json:"num"`

 Status       int32           `json:"status"`

 TTL          int32           `json:"ttl"`                     // Valid time

 SalePrice    float64         `json:"sale_price"`              // Record the sales price when adding cars

 SpecialPrice float64         `json:"special_price,omitempty"` // Add cart to specified price

 PostFree     bool            `json:"post_free,omitempty"`     // Is there free shipping

 Activities   []*ItemActivity `json:"activities,omitempty"`    // Record of activities attended

 AddTime      int64           `json:"add_time"`

 UpdateTime   int64           `json:"update_time"`

}



/ / activity

type ItemActivity struct {

 ActID    string `json:"act_id"`

 ActType  string `json:"act_type"`

 ActTitle string `json:"act_title"`

}

Copy the code

Let’s focus on the Item structure. The item_id field is the only tag that marks an Item in the shopping cart, because as we said before, the same SKU will have two different items in the shopping cart due to different channels; The parent_item_ID field is used to mark the parent-child relationship. Here, the possible tree structure is transformed into an order structure. We store both the parent and child items in order, and then use this field to associate them. Some of you might wonder, why is there an order ID field? We pay attention to their daily business, such as: to a single, advance deposit, such as this must be associated with an order, whether for qualification verification or data statistics. The rest of the fields are very general and I won’t go through them all.

The type of the field is modified according to your own needs.

Next, how to choose Redis storage structure, Redis commonly used Hash Table, set, ordered set, linked list, string five, we will analyze one by one.

First of all, there must be a key to mark which user owns the shopping cart. For simplicity, we assume that the key is uid:cart_type.

Let’s first look at using a Hash Table; HSET uid:cart_type sku ShoppingData; It looks fine, we can quickly locate an item based on the SKU and modify it, etc. But note that ShoppingData is a JSON string. If the user has a very large number of items in their shopping cart, we use HGETALL UID :cart_type to get the time complexity of O(n). And then you have to deserialize one by one in your code, order N complexity again.

If set is used, similar problems will be encountered. Each shopping cart is regarded as a set, and each element in the set is ShoppingData, which still needs to be deserialized one by one when it is taken to the code (deserialization is the cost). The ordered set and linked list will not be analyzed, so we can try to find the problems according to the above ideas.

It looks like we have no choice but to use String, so let’s see what String fit looks like. SET uid:cart_type ShoppingDataArr; We serialized all the data of the shopping cart into a string for storage, and the time complexity of each retrieval is O(1). Serialization and deserialization only need one time. It seems to be a very good choice. But in the use of you still need to pay attention to a few points.

  1. A single Value should not be too large, otherwise there will be a big key problem. Therefore, general shopping carts have upper limits, such as the number of items cannot exceed;
  2. The operation performance of Redis is improved, but the code is inconvenient to modify a single item. It must read all items each time and then find the corresponding item for modification. Here we can read the data from Redis and build a HashTable in memory to reduce the complexity of each traversal.

Online also see a lot of Redis data structure combination to store shopping cart data, but undoubtedly increased network overhead, compared with String is the most cost-effective.

conclusion

At this point, the realization of the shopping cart design is the end, which is about the design of the order table will be put into the order module separately.

For the whole shopping cart service, although there is no written to a specific interface, but the analysis to this step, I believe that everyone has a gap in mind, can combine their own business to achieve it.

There are some interesting places in the article, I suggest you to start to do it, any questions, we feel free to communicate.

  • An adaptation of the chain of responsibility model
  • Redis distributed transaction lock implementation

Next, we will finally design the order part. I hope you will continue to pay attention to us.

My official account is dayuTalk

Contact email: [email protected]

GitHub:github.com/helei112g