0 x00 preface

In the process of daily development, due to the unreasonable structure of the project, the invocation between micro-services needs to write each data transfer object, business code and so on into a unified two-party package. Especially in the case of multi-person collaboration, problems such as coverage, conflict and lack of dependence of two-party package are prone to occur, which reduces the work efficiency of the developer.

For this purpose, this paper describes how to disperse unreasonable common dependencies into various business modules through disassembly, so as to reduce and reduce the efficiency reduction caused by the coordination problem when different modules cooperate.

Objective: To improve work efficiency by adjusting project structure.

0x01 Analyze the current problem status

1.1 Unreasonable project subcontracting

Reasonable subcontracting should be the responsibility of each package is clear, can help the developer quickly locate the corresponding code. Unreasonable subcontracting will increase the cost of understanding the project.

Using an existing project as an example, the project structure hierarchy is shown below, with three layers as an example.

It is obvious that the subcontracting of the project is quite chaotic. It is easy to find that the responsibilities of the package are not clear, for example, configurations like security are not in config, the biz package contains a large number of business-neutral directories (util, constant, cache), and both core and project contain directories of the same name to a certain extent. Such as Biz, Web, models, etc., will make unfamiliar students increase a lot of learning and understanding costs.

1.2 Dependency management is chaotic and inconsistent

Currently, the dependency management of each project is completely independent, and the change of dependencies is very random, which can easily lead to dependency conflicts (as shown in the following figure). Conflicting dependencies need to be eliminated constantly. In addition, the dependent versions of each module are not related, and the dependent version numbers are not uniformly managed. It is possible that the dependent versions of each project are different.

Not only are there potential problems due to versioning, but it can be quite troublesome if it involves upgrading the version, such as Apache Log4j 2 remote code execution vulnerability (CVE-2021-44228), which requires upgrading the version number.

Complex dependencies with no version number management, each red line is a dependency conflict.

1.3 Tedious microservice invocation

In the current development process of microservices, developers need to write corresponding data transfer objects in the common binary package. In order to solve the problem of object transfer between different microservices.

🧑🏻 💻 👉 👉 📑

Generally speaking, a requirement can be developed by more than one developer, so it can be developed by more than one developer as shown below. At this point, two developers need to agree on a version number to ensure that the content of both sides is consistent, and there will be no problems such as mutual coverage of two packages.

🧑🏻 💻 👉 👉 📑 👈👈 💻 🧑🏻

In actual production process, different projects have different needs, and the demand of parallel could not only one, so in multiple projects, demand, many students at the same time under the condition of development, a common second package to solve all the back-end service object transfer problem is easy to appear, the second party package is frequent conflict, covering issues such as each other. Greatly reduce the efficiency of collaboration.

Project_01 🧑🏻💻 👉📑 👈 💻 🧑🏻 project_01

👇

Dependency conflicts ❌😭😭

☝ ️

Project_02 🧑🏻💻 👉📑👈 💻🧑🏻 project_02

On the other hand, the current business module does not provide a binary package for the corresponding RPC calls, but each application writes its own repeated interface calls, thus wasting a lot of time.

0x02 Solution

2.1 Problem Summary

  • The scope of the project structure is not clear, the cost of understanding is high, and the cost of new member intervention is high.
  • Dependency package management is chaotic and inconsistent
  • All data transfer objects need to be written in a common binary package, which is difficult to maintain and increases collaboration costs
  • The RPC call interface needs to be written repeatedly, which increases the workload

To solve the above problems, optimize the existing project structure.

  1. Establish a parent dependency management to unify the dependency versions of a project.
  2. Reprogramming the corresponding functions of each package, each project provides its own interface to the two packages. including rpc Interface and data transfer objects, each project maintains its own api Two packages.

The preparatory work

IntelliJ IDEA is used as the integrated development environment. The JDK defaults to OPEN JDK 1.8. The build tool is Maven 3.5 +

2.2 Structural design of the project

2.2.1 Parent-child POM dependency management design

The parent POM is used to manage versions of dependency packages, and the submodule uses specific dependency packages. Therefore, dependency management needs to be configured in the parent POM.

First, open Idea and select a new project. File -> new -> project -> maven

Enter each item as required and click Finish in the lower right corner to create a blank new project.

Next we create two subprojects, File -> new -> module -> maven. Everything else is the same as above. Here we plan API module to provide RPC interface, while BIZ module is responsible for implementing specific business logic.

In the figure below, we get a similar project structure.

Dependencies can be managed in the parent POM by using the dependencyManagement tag. In the submodule, you only need to configure the corresponding coordinates to automatically mark to the configured version number.


      

<project...>.<groupId>cn.yizhoucp</groupId>

<artifactId>family-services</artifactId>

<packaging>pom</packaging>


<properties>

    <fastjson.version>1.2.58</fastjson.version>

</properties>


<dependencyManagement>

    <dependencies>

        <dependency>

            <groupId>com.alibaba</groupId>

            <artifactId>fastjson</artifactId>

            <version>${fastjson.version}</version>

        </dependency>

    </dependencies>

</dependencyManagement>

</project>
Copy the code

Example of parent POM dependencies

A dependency reference in a child module, with a flag in the upper left corner indicating that the version number refers to a parent level dependency.

2.2.2 Using placeholders for Maven version management

In 2.2.1, the dependency management of sub-modules can be realized through the parent POM. However, if a submodule is upgraded, the corresponding submodules and dependencies need to be upgraded each time. To do this, you can use placeholders to uniformly manage the version numbers of submodules.

Both parent and import can manage dependencies through the dependencyManagement tag. But import does not inherit

PluginManagement, therefore, if you import spring-boot-starter-parent, you need to write additional configuration of spring-boot-Maven-plugin, otherwise you will find the problem of not finding the boot class after packaging.

The sample is as follows

The parent pom


      

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">

<parent>

    <artifactId>spring-boot-starter-parent</artifactId>

    <groupId>org.springframework.boot</groupId>

    <version>2.3.2. RELEASE</version>

</parent>



    <groupId>cn.yizhoucp</groupId>

    <artifactId>spring-template</artifactId>

    <packaging>pom</packaging>

    <version>${revision}</version>

    <modules>

        <module>${rootArtifactId}-api</module>

        <module>${rootArtifactId}-biz</module>

    </modules>

    

    <properties>

        <revision>1.0.0 - the SNAPSHOT</revision>

        <maven.compiler.source>8</maven.compiler.source>

        <maven.compiler.target>8</maven.compiler.target>

        <fastjson.version>1.2.58</fastjson.version>

        <maven_flatten_version>1.1.0</maven_flatten_version>

    </properties>

    

    <dependencyManagement>

        <dependencies>

        

            <! -- Spring Cloud Alibaba -->

            <dependency>

                <groupId>com.alibaba.cloud</groupId>

                <artifactId>spring-cloud-alibaba-dependencies</artifactId>

                <version>. 2.0.0 RELEASE</version>

                <type>pom</type>

                <scope>import</scope>

            </dependency>

            

            <dependency>

                <groupId>org.springframework.cloud</groupId>

                <artifactId>spring-cloud-dependencies</artifactId>

                <version>Greenwich.SR2</version>

                <type>pom</type>

                <scope>import</scope>

            </dependency>

            <! -- Submodule dependencies -->

            <dependency>

                <groupId>${groupId}</groupId>

                <artifactId>${rootArtifactId}-api</artifactId>

                <version>${project.version}</version>

            </dependency>

            <dependency>

                <groupId>${groupId}</groupId>

                <artifactId>${rootArtifactId}-biz</artifactId>

                <version>${project.version}</version>

            </dependency>

            

            <! -- Third-party dependency Management -->

            <dependency>

                <groupId>com.alibaba</groupId>

                <artifactId>fastjson</artifactId>

                <version>${fastjson.version}</version>

            </dependency>

        </dependencies>

    </dependencyManagement>

    

    <build>

        <defaultGoal>spring-boot:run</defaultGoal>

        <plugins>

            <! Add flatten-maven-plugin to flatten-maven-plugin -->

            <plugin>

                <groupId>org.codehaus.mojo</groupId>

                <artifactId>flatten-maven-plugin</artifactId>

                <version>${maven_flatten_version}</version>

                <configuration>

                    <updatePomFile>true</updatePomFile>

                    <flattenMode>resolveCiFriendliesOnly</flattenMode>

                </configuration>

                <executions>

                    <execution>

                        <id>flatten</id>

                        <phase>process-resources</phase>

                        <goals>

                            <goal>flatten</goal>

                        </goals>

                    </execution>

                    <execution>

                        <id>flatten.clean</id>

                        <phase>clean</phase>

                        <goals>

                            <goal>clean</goal>

                        </goals>

                    </execution>

                </executions>

            </plugin>

        </plugins>

    </build>

</project>
Copy the code

The child module pom

The Api module pom


      

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">

    <parent>

        <artifactId>spring-template</artifactId>

        <groupId>cn.yizhoucp</groupId>

        <version>${revision}</version>

    </parent>

    <modelVersion>4.0.0</modelVersion>



    <artifactId>${rootArtifactId}-api</artifactId>

    <dependencies>

        <dependency>

            <groupId>org.springframework.cloud</groupId>

            <artifactId>spring-cloud-starter-openfeign</artifactId>

        </dependency>

    </dependencies>

</project>
Copy the code

Biz module pom


      

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">

    <parent>

        <artifactId>spring-template</artifactId>

        <groupId>cn.yizhoucp</groupId>

        <version>${revision}</version>

    </parent>

    <modelVersion>4.0.0</modelVersion>



    <artifactId>${rootArtifactId}-biz</artifactId>



    <dependencies>



        <! -- Two bags -->

        <dependency>

            <groupId>${groupId}</groupId>

            <artifactId>${rootArtifactId}-api</artifactId>

        </dependency>





        <! -- Spring dependencies -->

        <dependency>

            <groupId>org.springframework.boot</groupId>

            <artifactId>spring-boot-starter-web</artifactId>

        </dependency>



        <dependency>

            <groupId>org.springframework.kafka</groupId>

            <artifactId>spring-kafka</artifactId>

        </dependency>





        <dependency>

            <groupId>com.alibaba.cloud</groupId>

            <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>

        </dependency>





        <dependency>

            <groupId>org.springframework.boot</groupId>

            <artifactId>spring-boot-configuration-processor</artifactId>

            <optional>true</optional>

        </dependency>



        <dependency>

            <groupId>org.springframework.boot</groupId>

            <artifactId>spring-boot-starter-logging</artifactId>

        </dependency>



        <dependency>

            <groupId>org.springframework.boot</groupId>

            <artifactId>spring-boot-starter-web</artifactId>

        </dependency>



        <dependency>

            <groupId>org.springframework.cloud</groupId>

            <artifactId>spring-cloud-starter-openfeign</artifactId>

        </dependency>



        <! -- Other dependencies -->

        <dependency>

            <groupId>org.jdom</groupId>

            <artifactId>jdom2</artifactId>

        </dependency>



        <dependency>

            <groupId>org.apache.commons</groupId>

            <artifactId>commons-lang3</artifactId>

        </dependency>



        <dependency>

            <groupId>mysql</groupId>

            <artifactId>mysql-connector-java</artifactId>

        </dependency>



        <dependency>

            <groupId>com.alibaba</groupId>

            <artifactId>fastjson</artifactId>

        </dependency>



        <dependency>

            <groupId>com.baomidou</groupId>

            <artifactId>mybatis-plus-boot-starter</artifactId>

        </dependency>



        <dependency>

            <groupId>ma.glasnost.orika</groupId>

            <artifactId>orika-core</artifactId>

        </dependency>



        <! Unit test dependencies -->



        <dependency>

            <! Unit test, we use H2 as database -->

            <groupId>com.h2database</groupId>

            <artifactId>h2</artifactId>

            <scope>test</scope>

        </dependency>

        <dependency>

            <groupId>com.baomidou</groupId>

            <artifactId>mybatis-plus-boot-starter-test</artifactId>

            <scope>test</scope>

        </dependency>

        <dependency>

            <groupId>org.springframework.boot</groupId>

            <artifactId>spring-boot-starter-test</artifactId>

            <scope>test</scope>

        </dependency>

        <dependency>

            <groupId>uk.co.jemos.podam</groupId>

            <artifactId>podam</artifactId>

            <scope>test</scope>

        </dependency>

    </dependencies>



    <build>

        <plugins>

            <plugin>

                <groupId>org.springframework.boot</groupId>

                <artifactId>spring-boot-maven-plugin</artifactId>

            </plugin>

        </plugins>

    </build>



</project>
Copy the code

Version management of parent and child modules is unified by using the ${revision} placeholder. If you need to manage a particular submodule, add a version tag.

Such as:

<artifactId>${rootArtifactId}-api</artifactId>
<version>1.0. 0.RELEASE</version>
Copy the code

When using placeholders for parent module management, the published binary package cannot find the parent POM because the placeholders in the parent dependency are not replaced. Therefore, the flatten-Maven-plugin plugin can be used to build the parent POM to solve the problem.

2.2.3 Specification of hierarchical package name and class name for projects

${project name}.${module name}.${subpackage name}

Example: cn. Yizhoucp. Family. API. The client

  • Controller package name: Web

    • The name of the class: the Controller
  • Service layer package name: Manager

    • The name of the class: Manager
  • The generic service processing layer package name is Serivce

    • The name of the class: Serivce
  • Configuration layer package name: config

    • The name of the class: the Config
  • DAO layer package name: mapper

    • The name of the class: Mapper
  • Database entity package name: entity
  • DTO Package name: DTO

    • The name of the class: DTO
  • View model object package name: VO

    • The name of the class: VO
  • Enumeration package name: enums
  • Constant package name: constant

    • The name of the class: Constants
  • Aspect implementation class package name: AOP

    • The name of the class: the Aspect
  • Exception package name: exception

    • The name of the class: the Exception
  • Spring Filter Package name: Filter

    • The name of the class: the Filter
  • Name of the security-related package: security
  • Utility class package name: util

    • The name of the class: Utils
  • Annotation package name: Annotation

Schematic diagram of project structure:

2.2.4 Api module design

The Api module mainly exposes the interface call of RPC, which is provided externally in the form of two-party package. It must contain the corresponding service code, data transfer object (DTO), and RPC interface. Therefore, the function of the API module is relatively simple and clear.

To use RPC calls, you need to package the API module into the corresponding Maven repository, referring to the MAVEN coordinates of the API module and the corresponding version number.

Such as:

<dependency>

    <groupId>cn.yizhoucp</groupId>

    <artifactId>spring-template-api</artifactId>

    <version>1.0.0 - the SNAPSHOT</version>

</dependency>
Copy the code

2.2.5 Biz module design

The Biz module is also a core function of the project. To some extent, though, it is possible to subdivide the BIZ module further, such as the common content submodule, configuration submodule, data persistence submodule, presentation submodule and so on.

However, it will greatly reduce work efficiency, for example, writing a simple function requires many more steps. Most importantly, it is not in line with the development habits of many developers, which will reduce the efficiency of development to a certain extent, which is contrary to the goal of improving work efficiency. Therefore, it is a wise choice to make a compromise to a certain extent at the present stage.

Therefore, Biz’s module design is as follows

2.3 Example

Example projects use Springboot 2.3.2+, RPC using Fegin, Orm using Mybatis- Plus, and mysql 5.6+ for the database

Create a database and a test table test_info

-- auto-generated definition

drop table if exists TEST_INFO;

create table if not exists TEST_INFO

(

    ID          BIGINT auto_increment,

    NAME        VARCHAR(255) not null,

    CREATE_TIME DATETIME     not null,

    UPDATE_TIME DATETIME     not null.constraint TEST_INFO_PK

        primary key (ID)

);
Copy the code
Write dal layer code

Entity class

BaseDO mainly encapsulates some common fields

@Data

public class BaseDO implements Serializable {



    @TableId(value = "id".type = IdType.AUTO)

    private Long id;



    @TableField(value = "create_time",jdbcType = JdbcType.DATE, fill = FieldFill.INSERT)

    private LocalDateTime createTime;



    @TableField(value = "update_time",jdbcType = JdbcType.DATE, fill = FieldFill.INSERT_UPDATE)

    private LocalDateTime updateTime;

}





@Data

@EqualsAndHashCode

@TableName(value = "test_info")

public class TestInfoDO extends BaseDO{



    private String name;

}
Copy the code

The Mapper class

Most of CRUD operations Mybatis-plus help us package, inheritance can be.

@Mapper

public interface TestInfoMapper extends BaseMapper<TestInfoDO> {}Copy the code
Write the Serivce layer code

Most interfaces of the Service layer can also be directly encapsulated by Mybatis- Plus. The example writes a findById to query content by Id.

public interface TestService extends IService<TestInfoDO> {



    TestInfoDO findById(Long id);

}



@Service

public class TestServiceImpl extends ServiceImpl<TestInfoMapper.TestInfoDO> implements TestService {





    @Override

    public TestInfoDO findById(Long id) {

        return baseMapper.selectOne(newQueryWrapper<TestInfoDO>() .lambda().eq(BaseDO::getId,id) ); }}Copy the code
Code the BIZ layer

The BIZ layer is where the main business logic is written. The business logic is completed by collating the data by calling different Service layers.

@Service

public class TestManager {



    @Autowired

    private TestService service;



    public TestInfoDTO findById(Long id) {

        return coverage(service.findById(id));

    }



    public boolean save(TestInfoDTO testInfoDTO) {

        return service.save(coverage(testInfoDTO));

    }

    

    private TestInfoDTO coverage(TestInfoDO source){

        TestInfoDTO target = new TestInfoDTO();

        BeanUtils.copyProperties(source,target);

        return target;

    }

    

    private TestInfoDO coverage(TestInfoDTO source){

        TestInfoDO target = new TestInfoDO();

        BeanUtils.copyProperties(source,target);

        returntarget; }}Copy the code
Write API and Web layer code

The Api interface is an exposed RPC interface

@FeignClient(name = "spring-template", contextId = "test-info")

public interface TestFeignService {



    @GetMapping("/test/findByid")

    Result<TestInfoDTO> findById(@RequestParam("id") Long id);





    @PostMapping("/test/save")

    Result<Boolean> save(@RequestBody TestInfoDTO entity);

}
Copy the code

DTO data is encapsulated according to service requirements.

@Data

@AllArgsConstructor

@NoArgsConstructor

@Builder

public class TestInfoDTO {

    private Long id;

    private String name;

    private LocalDateTime createTime;

    private LocalDateTime updateTime;



}
Copy the code

The Controller class mainly implements the interface to the API layer. If you don’t need the API’s interface, you can write the Controller class separately in the Web layer

@RestController

public class TestController implements TestFeignService {



    @Autowired

    private TestManager testManager;



    @Override

    public Result<TestInfoDTO> findById(Long id) {

        return RestBusinessTemplate.executeWithoutTransaction(() -> {

            return testManager.findById(id);

        });

    }



    @Override

    public Result<Boolean> save(TestInfoDTO entity) {

        return RestBusinessTemplate.executeWithoutTransaction(() -> {

            returntestManager.save(entity); }); }}Copy the code

The writing is completed and the project runs successfully. The request interface functions are normal.

Use CURL to insert data into a POST request

curl -X POST -d '{"name": "name1"}' -H 'Content-Type: application/json' http://localhost:9132/test/save
Copy the code

{“code”:”000000″,”message”:”success”,”data”:true,”serverTime”:1649761604271,”extData”:null}

Use CURL to query data successfully

{“id”:1,”name”:”name”,”createTime”:{“dayOfMonth”:6,”dayOfWeek”:”WEDNESDAY”,”dayOfYear”:96,”monthValue”:4,”hour”:9,”minut e”:34,”second”:30,”year”:2022,”month”:”APRIL”,”nano”:0,”chronology”:{“id”:”ISO”,”calendarType”:”iso8601″}},”updateTime”: {“dayOfMonth”:6,”dayOfWeek”:”WEDNESDAY”,”dayOfYear”:96,”monthValue”:4,”hour”:9,”minute”:34,”second”:30,”year”:2022,”mont h”:”APRIL”,”nano”:0,”chronology”:{“id”:”ISO”,”calendarType”:”iso8601″}}}

0 x03 summary

journey

In the early design process, we tried to divide the project into seven sub-modules, focusing on the business content, and constraining and standardizing the project structure through the mandatory modules.

But to some extent, mandatory constraints and norms will inevitably lead to the reduction of efficiency to a certain extent. The main goal of this project structure optimization was to improve the development efficiency of work, which also led to excessive design deviation from the original goal.

This also led to some criticism from most of the students. After re-thinking and talking with most of the students in detail, on the one hand, I found that many students had done a lot of reconstruction and adjustment for the modules they managed.

In other words, the structure of a project can be regulated to some extent by a common specification. Then plan out the thinking of the original eliminating, based on the structure of existing projects and to make the change of adjust measures to local conditions, provide a relative advice more standardized template, on the one hand, able to provide the reference sample to regulate structure, on the other hand also can adapt to different project conditions appropriate adjustments, in order to minimize the change of project structure, to solve the existing problem.

Conclusion regular

Bringing about some level of institutional change in a team or organization is necessarily about solving a major problem. It is good to solve problems, but at the same time, we should also consider whether it brings new problems. It is the best practice for any complete theory to make changes in accordance with local conditions when facing the inevitable imperfect reality.

Template code: git. Yizhoucp. Cn/microservic…


Architecture evolution from MVC to DDD – Mu Xiaofeng – Blog Garden

DDD concept reference

Practice of domain driven design in Internet business development

Guide to Maven Archetype | Baeldung

Spring – Best practices for project development