第4章 项目初始化:Maven多模块工程搭建

好,咱们开始动手了。

前面几章聊了CRM系统的整体架构设计,也画了领域模型。但说实话,那些都是纸面上的东西。真正落地,得从项目初始化开始。我个人习惯,所有微服务项目都从Maven多模块工程起步。为什么?因为微服务本身就是「分而治之」的思想,多模块正好对应这种拆分。

4.1 为什么选择Maven多模块

你想想看,一个CRM系统,少说也有十几个微服务。如果每个服务都独立建一个Maven项目,版本管理会变成噩梦。我见过一个团队,每个服务各自维护一份pom.xml,结果Spring Boot版本从2.3到2.5都有,依赖冲突修了整整两周。

多模块的好处很明显:

  • 统一管理:父工程管版本,子模块只管业务
  • 构建顺序:Maven自动处理模块间的依赖顺序
  • 代码复用:公共工具类、DTO、Feign接口,放在common模块里

核心原则:父工程只做版本控制和公共配置,不写业务代码。子模块各司其职,互不干扰。

4.2 父工程搭建

先建一个空的Maven项目,packaging类型选pom。嗯,这里要注意,父工程不需要src目录,删掉就好。

<?xml version="1.0" encoding="UTF-8"?>
<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 
         http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.crm</groupId>
    <artifactId>crm-parent</artifactId>
    <version>1.0.0-SNAPSHOT</version>
    <packaging>pom</packaging>

    <name>CRM Parent Project</name>
    <description>基于微服务的CRM系统父工程</description>

    <modules>
        <module>crm-common</module>
        <module>crm-user-service</module>
        <module>crm-order-service</module>
        <module>crm-customer-service</module>
        <module>crm-gateway</module>
    </modules>
</project>

这里我列了5个模块,实际项目会更多。但记住,modules标签里只放顶层模块,子模块的子模块不用写在这里。

4.3 统一版本控制

版本控制是父工程的核心职责。我在项目中吃过亏——Spring Boot和Spring Cloud版本不兼容,启动报错查了半天。后来我养成了一个习惯:用properties标签统一管理所有版本号。

<properties>
    <!-- 基础框架版本 -->
    <spring-boot.version>2.7.12</spring-boot.version>
    <spring-cloud.version>2021.0.7</spring-cloud.version>
    <spring-cloud-alibaba.version>2021.0.5.0</spring-cloud-alibaba.version>

    <!-- 数据库相关 -->
    <mybatis-plus.version>3.5.3</mybatis-plus.version>
    <mysql-connector.version>8.0.33</mysql-connector.version>

    <!-- 工具类 -->
    <hutool.version>5.8.20</hutool.version>
    <guava.version>32.0.1-jre</guava.version>

    <!-- 其他 -->
    <lombok.version>1.18.28</lombok.version>
    <mapstruct.version>1.5.5.Final</mapstruct.version>
</properties>

我的习惯:版本号统一放在properties里,用注释分组。这样升级版本时,改一个地方就行,不会漏掉。

然后,在dependencyManagement里引入这些依赖:

<dependencyManagement>
    <dependencies>
        <!-- Spring Boot BOM -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-dependencies</artifactId>
            <version>${spring-boot.version}</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>

        <!-- Spring Cloud BOM -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-dependencies</artifactId>
            <version>${spring-cloud.version}</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>

        <!-- 项目内部模块 -->
        <dependency>
            <groupId>com.crm</groupId>
            <artifactId>crm-common</artifactId>
            <version>${project.version}</version>
        </dependency>
    </dependencies>
</dependencyManagement>

注意看,内部模块的版本用了${project.version},这样所有子模块版本自动和父工程保持一致。我曾经见过有人手动写版本号,结果升级时漏改了一个,编译报错找了一下午。

4.4 子模块依赖管理

子模块的pom.xml就清爽多了。以crm-user-service为例:

<parent>
    <groupId>com.crm</groupId>
    <artifactId>crm-parent</artifactId>
    <version>1.0.0-SNAPSHOT</version>
</parent>

<artifactId>crm-user-service</artifactId>
<packaging>jar</packaging>

<dependencies>
    <!-- 内部依赖 -->
    <dependency>
        <groupId>com.crm</groupId>
        <artifactId>crm-common</artifactId>
    </dependency>

    <!-- Spring Boot Web -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>

    <!-- 数据库 -->
    <dependency>
        <groupId>com.baomidou</groupId>
        <artifactId>mybatis-plus-boot-starter</artifactId>
    </dependency>

    <!-- 工具类 -->
    <dependency>
        <groupId>cn.hutool</groupId>
        <artifactId>hutool-all</artifactId>
    </dependency>

    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <optional>true</optional>
    </dependency>
</dependencies>

看到了吗?子模块不需要写版本号。版本号全部由父工程统一控制。这就是dependencyManagement的威力——子模块只管声明「我要什么」,不用管「什么版本」。

避坑指南:我曾经在子模块里直接写了版本号,覆盖了父工程的配置。结果升级Spring Boot时,子模块的版本没跟着变,导致编译失败。记住:子模块永远不要写版本号,除非你明确需要覆盖父工程的配置。

4.5 公共模块设计

crm-common模块是所有微服务的公共依赖。它不启动,只提供代码复用。我一般放这些东西:

  • 统一返回结果Result<T>类,包含code、message、data
  • 异常定义:业务异常、参数校验异常等
  • 工具类:日期处理、字符串处理、加密工具
  • DTO:跨服务传输的数据对象
  • Feign接口:服务间调用的API定义

举个例子,统一返回结果:

package com.crm.common.result;

import lombok.Data;
import java.io.Serializable;

@Data
public class Result<T> implements Serializable {
    private Integer code;
    private String message;
    private T data;

    public static <T> Result<T> success(T data) {
        Result<T> result = new Result<>();
        result.code = 200;
        result.message = "success";
        result.data = data;
        return result;
    }

    public static <T> Result<T> error(Integer code, String message) {
        Result<T> result = new Result<>();
        result.code = code;
        result.message = message;
        return result;
    }
}

这个类所有服务都会用到。放在common模块里,每个服务引入common依赖就能直接用。避免了每个服务各自写一套返回结果,接口风格不统一的问题。

4.6 构建与验证

项目搭好后,执行一次全量构建:

mvn clean install -DskipTests

如果一切顺利,你会看到类似这样的输出:

[INFO] Reactor Summary:
[INFO] 
[INFO] CRM Parent Project ............................ SUCCESS
[INFO] crm-common ................................... SUCCESS
[INFO] crm-user-service ............................. SUCCESS
[INFO] crm-order-service ............................ SUCCESS
[INFO] crm-customer-service ......................... SUCCESS
[INFO] crm-gateway .................................. SUCCESS
[INFO] BUILD SUCCESS

看到这个「BUILD SUCCESS」,说明你的多模块工程搭好了。嗯,这一步虽然简单,但基础打牢了,后面写代码才顺畅。

总结一下:父工程管版本,子模块写业务,common模块做复用。这三层结构,是微服务项目的基础骨架。我做了这么多年项目,这套结构几乎没变过——因为它确实好用。

下一章,咱们开始搭建Nacos注册中心和配置中心。服务发现和配置管理,是微服务的第一步。