Using DDD to develop classification module in e-commerce system

Posted by BobcatM on Fri, 07 Feb 2020 09:25:21 +0100

Using DDD to develop the classification module of e-commerce system.

We will discuss two commodity classifications in e-commerce system, namely, the commodity classification of e-commerce system category and the commodity classification customized by merchants.
The domain model is used to implement these two classifications.

##Hypothetical business

  • case 1: in order to find goods more conveniently, develop a set of commodity categories for e-commerce system, and then classify goods according to commodity categories.
  • case 2: merchant stores also need to support commodity classification, but they can only let merchants customize commodity classification.
  • Product classification also supports hierarchical relationships. For example, men's shoes are a category. Men's shoes can be divided into Doudou shoes, old Beijing cloth shoes and so on.
    At this time, a spiritual guy wants to buy a pair of Doudou shoes to shake flower hands. He just needs to choose men's shoes, and then choose Doudou shoes category to find many styles of Doudou shoes.

Subdomain partition

Before dividing subdomains, I want to explain to you what is Domain Driven Design?

Take our specific example, e-commerce system is a domain. According to the business driven model of e-commerce system, a set of models is called domain driven design.
At this time, if we continue to drill into the e-commerce system, we will find more domains, such as customers, stores, orders, categories and so on.
These single domains only reflect a part of the whole e-commerce system, so we call these domains sub domains.

According to the above two commodity classifications we want to develop, we can see that they are organized in the category sub domain and the store sub domain respectively.

General language

When it comes to language, no one is unfamiliar, because you need to use it to communicate every day.
So in order to facilitate communication between customers, domain experts and developers, we need to form a set of languages,
Such a language that enables a team to communicate unobstructed is a common language.

For example, the common languages in e-commerce system are:
E-commerce system (mall), catalog sub domain (catalog), store sub domain (store)
Category and custom collection.

Split module

Now when we mention the development of e-commerce system, it is distributed, highly scalable, highly scalable, highly concurrent and so on.
So we split catalog and store into two subsystems.
Then we divide the modules in each subsystem. First, we need to develop domain modules,
Then, in order to adapt the domain module to a certain port, we develop the adapter module for the specific port.
For example, in order to adapt the catalog module to restful, we will develop a catalog rest module
To adapt to rpc, we need to develop a catalog rpc module.

The details are as follows:

This UML class diagram is from the e-commerce (mall) system of our upcoming open source mall foundry.
For the convenience of explanation, I simplified the class diagram and only kept the business that we are concerned about now.

At this point, I also want to say that the initial purpose of creating the mall foundry is to provide a one-stop solution for the development of e-commerce system.
mall foundry is an out of the box, pluggable component library.
It can be run individually, distributed and embedded in the existing system.

Project structure

According to the above design drawings, we have created the following projects:

This project code is also copied from the project of MAL foundry, and it is also simplified.

Store subsystem

First, we will analyze and explain the domain business in the store sub domain. There is only one domain model in this sub-system, custom collection,
Because there are not many custom commodity classifications in a shop, which have little impact on performance, when we view the commodity classifications of a shop, we will load all the classifications at one time.
So we add a one to many self associated children attribute to the CustomCollection class. The code is as follows:

CustomCollection

@Getter
@Setter
@NoArgsConstructor
@JsonPropertyOrder({"id", "name", "position", "children"})
@Entity
@Table(name = "store_custom_collection")
@Inheritance(strategy = InheritanceType.SINGLE_TABLE)
@DiscriminatorColumn(name = "type_")
public class CustomCollection implements Comparable<CustomCollection> {

    @JsonIgnore
    private StoreId storeId;

    @Id
    @GeneratedValue
    @Column(name = "id_")
    private Integer id;

    @Column(name = "name_")
    private String name;

    @Column(name = "position_")
    private Integer position;

    @OneToMany(cascade = CascadeType.ALL, orphanRemoval = true)
    @JoinColumn(name = "parent_id_")
    private List<ChildCustomCollection> children = new ArrayList<>();

    public CustomCollection(String name) {
        this.setName(name);
    }

    public CustomCollection(StoreId storeId, String name) {
        this.setStoreId(storeId);
        this.setName(name);
    }

    @Embedded
    public StoreId getStoreId() {
        return storeId;
    }

    public ChildCustomCollection createChildCollection(String name) {
        return new ChildCustomCollection(this, name);
    }

    public void addChildCollection(ChildCustomCollection collection) {
        collection.setPosition(Integer.MAX_VALUE);
        collection.setParent(this);
        this.getChildren().add(collection);
        CustomCollectionPositions.sort(this.getChildren());
    }

    public void removeChildCollection(ChildCustomCollection collection) {
        this.getChildren().remove(collection);
    }

    public void clearChildCollections() {
        this.getChildren().clear();
    }
}

TopCustomCollection

@NoArgsConstructor
@Entity
@DiscriminatorValue("top_custom_collection")
public class TopCustomCollection extends CustomCollection {

    public TopCustomCollection(StoreId storeId, String name) {
        super(storeId, name);
    }
}

ChildCustomCollection

@NoArgsConstructor
@Entity
@DiscriminatorValue("child_custom_collection")
public class ChildCustomCollection extends CustomCollection {

    @JsonIgnore
    @ManyToOne
    private CustomCollection parent;

    public ChildCustomCollection(String name) {
        super(name);
    }

    public ChildCustomCollection(CustomCollection parent, String name) {
        super(parent.getStoreId(), name);
        this.setParent(parent);
    }

    @Embedded
    @Override
    public StoreId getStoreId() {
        return Objects.nonNull(this.getParent()) ? this.getParent().getStoreId() : super.getStoreId();
    }
}

In order to explain the following contents more thoroughly, I want to talk about the difference between data model and domain model.

When it comes to data model, you may think of E-R model or table in database. For this model, it only describes static data without behavior.
What happens without behavior?

Let's take the business of adding a subcategory for commodity classification. Using the data model, we will create a CustomCollection object,
Then set the parent ID and insert it into the table using SQL.
This is not reflected in object-oriented, but just procedural insertion of data. The code is as follows:

    @Transactional
    @Rollback
    @Test
    public void testAddCollectionUseDataModel() {
        ChildCustomCollection collection = new ChildCustomCollection();
        collection.setStoreId(new StoreId("mall-foundry"));
        collection.setParent(null); // collection.setParentId("parent id");
        collection.setName("data model collection");
        customCollectionRepository.save(collection);
    }

Next we use the domain model to develop the same business.
To add a subcategory to a commodity classification, you must add a subcategory object through the commodity classification object,
So we should write a method to add child collection in the CustomCollection class, encapsulating the details of adding child objects inside the method.
Finally, we use the CustomCollection object to call the addChildCollection method to add subcategories. The code is as follows:

    @Transactional
    @Rollback
    @Test
    public void testAddCollectionUseDomainModel() {
        CustomCollection collection = customCollectionRepository.findById(1);
        collection.addChildCollection(new ChildCustomCollection("child custom collection"));
    }

Is the domain model more suitable for business needs?
You can see other details about product classification in the merchant sub domain Sample code.

###Catalog subsystem

Category and
The business of customer collection in the store sub domain is similar.
But the former is the commodity category classification of the whole system, and the latter is only limited in the store sub domain.

Because catalog is used to serve the whole system, there are many subdirectories under each top category,
Getting all the directories at once will definitely affect performance, so we load the top-level directory and the subdirectory in two times.
That is, get the top-level directory collection first, and then get the subdirectory collection again when you select a top-level directory.

First, the ORM we used was spring data JPA, so we added the @ onetoman annotation to the children attribute,
@One to many is lazy by default, so we don't need to do too much.

In the presentation layer, we use spring mvc's @ RestController to publish the RESTful interface. The data format is json.
To ignore the children attribute in response to the top category,
We added the @ JsonIgnore annotation to the children attribute of the Category derived class TopCategory.
The code is as follows:

Category

@Getter
@Setter
@NoArgsConstructor
@Entity
@Table(name = "catalog_category")
@Inheritance(strategy = InheritanceType.SINGLE_TABLE)
@DiscriminatorColumn(name = "type_")
public class Category implements Comparable<Category> {

    @Id
    @GeneratedValue
    @Column(name = "id_")
    private Integer id;

    @Column(name = "name_")
    private String name;

    @Column(name = "position_")
    private Integer position;

    @OneToMany(cascade = CascadeType.ALL, orphanRemoval = true)
    @JoinColumn(name = "parent_id_")
    private List<ChildCategory> children = new ArrayList<>();

    public Category(String name) {
        this.name = name;
    }

    public ChildCategory createChildCategory(String name) {
        return new ChildCategory(this, name);
    }

    public void addChildCategory(ChildCategory childCategory) {
        childCategory.setPosition(Integer.MAX_VALUE);
        childCategory.setParent(this);
        this.getChildren().add(childCategory);
        CategoryPositions.sort(this.getChildren());
    }

    public void removeChildCategory(ChildCategory childCategory) {
        this.getChildren().remove(childCategory);
    }

    public void clearChildCategories() {
        this.getChildren().clear();
    }
}

TopCategory

@Entity
@DiscriminatorValue("top_category")
@NoArgsConstructor
public class TopCategory extends Category {

    @JsonIgnore
    @Override
    public List<ChildCategory> getChildren() {
        return super.getChildren();
    }

    public TopCategory(String name) {
        super(name);
        this.setPosition(Integer.MAX_VALUE);
    }
}

ChildCategory

@Getter
@Setter
@NoArgsConstructor
@Entity
@DiscriminatorValue("child_category")
public class ChildCategory extends Category {

    @JsonIgnore
    @ManyToOne
    private Category parent;

    public ChildCategory(String name) {
        super(name);
    }

    public ChildCategory(Category parent, String name) {
        super(name);
        this.setParent(parent);
    }
}

For more details on product categories in the catalog subfield, you can see Sample code.

summary

This article uses a simple example to show how to develop a business using a domain model.

Maybe you don't think it's special at this time, just move some method operations to the model,
I can do the same with the data model.

When the back-end developers are faced with a requirement, we are not only to achieve the effect, but also to achieve the business behind the effect.
When you are to solve the domain business, not just to show the effect coding, you can feel the significance of the domain model.
At this point, you can realize that the previous data model only exists for transmission and does not show domain business.

End

The above article only represents my personal thinking and is forbidden to reprint. If there is any mistake, please point out.

If you have any questions, please add QQ group to discuss:

Published 4 original articles, praised 0, visited 68
Private letter follow

Topics: Attribute Spring REST Database