How to Design in Clean Architecture Way, Part 1

ChunTing Wu - Jan 24 '22 - - Dev Community

There are many articles and books talking about Clean Architecture, but the focus is on why and what, such as why to use an onion architecture, what each layer represents, and what is the SOLID principle.

Onion Architecture

However, developers actually want to pay more attention to how, that is, how to write a good program architecture according to those design principles.

In this article, we try to solve a common architectural problem in clean architecture way.

In many mobile games or commercial websites, in order to improve user retention, a sign-in mission is designed. If you sign in, you will get some rewards. When you sign in continuously for a certain number of days, there will be additional gift boxes that can be opened, and you will get bonuses after opening. Let's try to fulfill this requirement together.

Problem Domain

Users who log in continuously will get diamonds, as shown in the following table:

Days 1st 2nd 3rd 4th 5th 6th 7th
Diamond 10 10 15 15 30 30 100
Extra Box 0 100 0 0 100 0 100

To simplify the problem, let's get a fixed amount of 100 diamonds the gift box is opened each time. For example, if a user signs in for four consecutive days, the user can get 30 and a gift box on the fifth day of sign-in.

Every time the user enters the homepage, he will see a sign-in map, telling the user how many consecutive sign-ins have been completed in this cycle and how many rewards have been received.

Design Phase

There are two different design approaches, data-oriented design and domain-driven design. We will start talking about data-oriented design and tell you why it's not a mainstream design solution.

Data-oriented Design (Can't Say Good Approach)

The data-oriented design means that when we see a problem, we first think about how to store the data, and then manipulate the defined data format to try to solve the original problem.

Therefore, after getting the question, the first step is to think about what database to use, what schema it will have, and what format the data will exist in.

Let's take the sign-in mission as an example. Suppose we design on a small monolith, usually with a relational database, so we first define a table to realize our problem. Define a sign-in table to store the time of each sign-in, so that you can know how many rewards you can get based on the previous sign-in records.

User Sign Date
John 2022-01-01
Mary 2022-01-01
John 2022-01-02
Mary 2022-01-02
John 2022-01-03

From the above table, if today is 2022-01-04, then John can get 15 diamonds when he signs in, and Mary will start a new cycle because she has not signed in consecutively and only get 10 diamonds.

The logic of the mission will be to filter the sign-in date of a specific user and sort them by date, then retrieve the latest N records. By calculating the total days of the consecutive sign-ins, and then you can know what reward to get this time.

There is a problem with the such design. When the user's sign-in is not interrupted at all, i.e., the number of consecutive sign-in days is greater than N, then the amount of data is not enough to determine the current reward. Nevertheless, we do not want to expand N to avoid pulling too much data makes the database a bottleneck. Thus, we should try making some changes to the original schema, such as adding a new field, diamond.

User Sign Date Diamond
John 2022-01-01 10
John 2022-01-02 10
John 2022-01-03 15
John 2022-01-04 15
John 2022-01-05 30
John 2022-01-06 30
John 2022-01-07 100
John 2022-01-08 10

For John, signing in on 2022-01-09 will get 10 diamonds because we know the end of the previous cycle was 2022-01-07.

The story is not over yet. When the number of users grow, the performance of RDBMS will be tough. Every time you enter the homepage, you have to pull data from the database, run a complex calculation, and finally see the result. This process is very inefficient.

In order to improve the efficiency of the homepage, most projects will introduce a cache to record the status of the entire sign-in. However, how do you know if the cache is invalid? Or the cache is actually not invalid, but distorted. I have covered this problem in a previous article Resilient Caching. By adding an integrity, we can distinguish whether the data is reliable. Once the data is corrupted, we will rebuild the data from the RDBMS.

Summary of Data-oriented Design

From the above introduction, we found that even the cache has many aspects to consider, the whole data-oriented design is very complicated, and the process is very struggled.

Hence, domain-driven design was born.

In part 2, we will start designing from Entity, covering use cases, then implementing the unit tests like TDD, and finally completing our mission. I will provide a better design process, and you can follow the steps to simplify every user stories you will encounter. Let call it a day, and see you next week.

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Terabox Video Player