ddd-lite-codegen 樣板代碼終結者

ddd-lite-codegen

基於 ddd lite 和 ddd lite spring 體系構建,基於領域模型對象自動生成其餘非核心代碼。

0. 運行原理

ddd lite codegen 構建於 apt 技術之上。

框架提供若干註解和註解處理器,在編譯階段,自動生成所需的 Base 類。這些 Base 類隨着領域對象的重構而變化,從而大大減小樣板代碼。
若有特殊需求,能夠經過子類進行擴展,而無需修改 Base 類的內容(每次編譯,Base 類都會自動生成)。html

1. 配置

該框架採用兩步處理,第一步由 maven 的 apt plugin 完成,第二步由編譯器調用 apt 組件完成。java

對於 maven 項目,須要添加相關依賴和插件,具體以下:web

<?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.geekhalo</groupId>
    <artifactId>gh-ddd-lite-demo</artifactId>
    <version>1.0.0-SNAPSHOT</version>

    <parent>
        <groupId>com.geekhalo</groupId>
        <artifactId>gh-base-parent</artifactId>
        <version>1.0.0-SNAPSHOT</version>
    </parent>

    <properties>
        <service.name>demo</service.name>
        <server.name>gh-${service.name}-service</server.name>
        <server.version>v1</server.version>
        <server.description>${service.name} Api</server.description>
        <servlet.basePath>/${service.name}-api</servlet.basePath>
    </properties>

    <dependencies>
        <!-- 添加 ddd 相關支持-->
        <dependency>
            <groupId>com.geekhalo</groupId>
            <artifactId>gh-ddd-lite</artifactId>
            <version>1.0.0-SNAPSHOT</version>
        </dependency>
        <dependency>
            <groupId>com.geekhalo</groupId>
            <artifactId>gh-ddd-lite-spring</artifactId>
            <version>1.0.0-SNAPSHOT</version>
        </dependency>

        <!-- 添加 code gen 依賴,將自動啓用 EndpointCodeGenProcessor 處理器-->
        <!--編譯時有效便可,運行時,不須要引用-->
        <dependency>
            <groupId>com.geekhalo</groupId>
            <artifactId>gh-ddd-lite-codegen</artifactId>
            <version>1.0.1-SNAPSHOT</version>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>com.querydsl</groupId>
            <artifactId>querydsl-apt</artifactId>
            <scope>provided</scope>
        </dependency>

        <!-- 持久化主要由 Spring Data 提供支持-->
        <dependency>
            <groupId>com.querydsl</groupId>
            <artifactId>querydsl-mongodb</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-mongodb</artifactId>
        </dependency>

        <!-- 添加測試支持-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>

        <!-- 添加 Swagger 支持-->
        <dependency>
            <groupId>io.springfox</groupId>
            <artifactId>springfox-swagger2</artifactId>
        </dependency>
        <dependency>
            <groupId>io.springfox</groupId>
            <artifactId>springfox-swagger-ui</artifactId>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>com.mysema.maven</groupId>
                <artifactId>apt-maven-plugin</artifactId>
                <version>1.1.3</version>
                <executions>
                    <execution>
                        <goals>
                            <goal>process</goal>
                        </goals>
                        <configuration>
                            <outputDirectory>target/generated-sources/java</outputDirectory>
                            <processors>
                                <!--添加 Querydsl 處理器-->
                                <processor>com.querydsl.apt.QuerydslAnnotationProcessor</processor>
                                <!--添加 DDD 處理器-->
                                <processor>com.geekhalo.ddd.lite.codegen.DDDCodeGenProcessor</processor>
                            </processors>
                        </configuration>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>

</project>

2. GenCreator

領域對象爲限界上下文中受保護對象,絕對不該該將其暴露到外面。所以,在建立一個新的領域對象時,須要一種機制將所需數據傳遞到模型中。

經常使用的機制就是將建立時所需數據封裝成一個 dto 對象,經過這個 dto 對象來傳遞數據,領域對象從 dto 中提取所需數據,完成對象建立工做。spring

creator 就是這種特殊的 Dto,在封裝建立對象所需的數據的同時,提供數據到領域對象的綁定操做。mongodb

2.1 常規作法

假設,如今有一個 Person 類:apache

@Data
public class Person {
    private String name;
    private Date birthday;
    private Boolean enable;
}

咱們須要建立新的 Person 對象,比較正統的方式即是,建立一個 PersonCreator,用於封裝所需數據:api

@Data
public class PersonCreator {
    private String name;
    private Date birthday;
    private Boolean enable;
}

而後,在 Person 中添加建立方法,如:app

public static Person create(PersonCreator creator){
    Person person = new Person();
    person.setName(creator.getName());
    person.setBirthday(creator.getBirthday());
    person.setEnable(creator.getEnable());
    return person;
}

你們有沒有發現問題:框架

  1. Person 和 PersonCreator 包含的屬性基本相同
  2. 若是在 Person 中添加、移除、修改屬性,會同時調整三處(Person、PersonCreator、create 方法),遺漏任何一處,都會致使邏輯錯誤

對於這種機械並且有規律的場景,是否能夠採用自動化方式完成?dom

2.2 @GenCreator

@GenCreator 即是基於此場景產生的。
2.2.1 啓用 GenCreator

新建 Person 類,在類上添加 @GenCreator 註解。

@GenCreator
@Data
public class Person extends JpaAggregate{
    private String name;
    private Date birthday;
    private Boolean enable;
}
2.2.2 編譯代碼,生成 BaseXXXXCreator 類

執行 mvn clean compile 命令,在 target/generated-sources/java 對應包下,會出現一個 BasePersonCreator 類,以下:

@Data
public abstract class BasePersonCreator<T extends BasePersonCreator> {
  @Setter(AccessLevel.PUBLIC)
  @Getter(AccessLevel.PUBLIC)
  @ApiModelProperty(
      value = "",
      name = "birthday"
  )
  private Date birthday;

  @Setter(AccessLevel.PUBLIC)
  @Getter(AccessLevel.PUBLIC)
  @ApiModelProperty(
      value = "",
      name = "enable"
  )
  private Boolean enable;

  @Setter(AccessLevel.PUBLIC)
  @Getter(AccessLevel.PUBLIC)
  @ApiModelProperty(
      value = "",
      name = "name"
  )
  private String name;

  public void accept(Person target) {
    target.setBirthday(getBirthday());
    target.setEnable(getEnable());
    target.setName(getName());
  }
}

該類含有與 Person 同樣的屬性,並提供 accept 方法,對 person 對象執行對應屬性的 set 操做。

2.2.3 構建 PersonCreator

基於 BasePersonCreator 建立 PersonCreator 類。

public class PersonCreator extends BasePersonCreator<PersonCreator>{
}
2.2.4 添加靜態 create 方法

使用 PersonCreator 爲 Person 提供靜態工廠方法。

@GenCreator
@Data
public class Person extends JpaAggregate{
    private String name;
    private Date birthday;
    private Boolean enable;

    public static Person create(PersonCreator creator){
        Person person = new Person();
        creator.accept(person);
        return person;
    }
}

之後 Person 屬性的變化,將自動應用於 BasePersonCreator 中,程序的其餘部分沒有任何改變。

2.3 運行原理

@GenCreator 運行原理以下:

  1. 自動讀取當前類的 setter 方法;
  2. 篩選 publicprotected 訪問級別的 setter 方法,將其做爲屬性添加到 BaseXXXCreator 類中;
  3. 建立 accept 方法,讀取 BaseXXXXCreator 的屬性,並經過 setter 方法寫回業務數據。

對於不須要添加到 Creator 的 setter 方法,可使用 @GenCreatorIgnore 忽略該方法。

細心的同窗可能注意到,在 BaseXXXXCreator 類的屬性上存在 @ApiModelProperty 註解,該註解爲 Swagger 註解,用於生成 Swagger 文檔。
咱們可使用 @Description 註解,標註字段描述信息,這些信息會自動添加的 Swagger 文檔中。

3. GenUpdater

GenUpdater 和 GenCreator 很是類似,主要應用於對象修改場景。

相對於建立,對象修改場景有點特殊,即對 null 的處理,當用戶傳遞 null 進來,不知道是屬性不修改仍是屬性設置爲 null。針對這種場景,經常使用方案是將其包裝在一個 Optional 中,若是 Optional 對應的屬性爲 null,表示對該屬性不作處理;若是 Optional 中包含的 value 爲null,表示將屬性值設置爲 null。

3.1 啓用 GenUpdater

在 Person 類上添加 @GenUpdater 註解。

@GenUpdater
@GenCreator
@Data
public class Person extends JpaAggregate{
    @Description("名稱")
    private String name;
    private Date birthday;
    private Boolean enable;

    public static Person create(PersonCreator creator){
        Person person = new Person();
        creator.accept(person);
        return person;
    }
}

3.2 編譯代碼,生成 Base 類

執行 mvn clean compile, 生成 BasePersonUpdater。

@Data
public abstract class BasePersonUpdater<T extends BasePersonUpdater> {
  @Setter(AccessLevel.PRIVATE)
  @Getter(AccessLevel.PUBLIC)
  @ApiModelProperty(
      value = "",
      name = "birthday"
  )
  private DataOptional<Date> birthday;

  @Setter(AccessLevel.PRIVATE)
  @Getter(AccessLevel.PUBLIC)
  @ApiModelProperty(
      value = "",
      name = "enable"
  )
  private DataOptional<Boolean> enable;

  @Setter(AccessLevel.PRIVATE)
  @Getter(AccessLevel.PUBLIC)
  @ApiModelProperty(
      value = "名稱",
      name = "name"
  )
  private DataOptional<String> name;

  public T birthday(Date birthday) {
    this.birthday = DataOptional.of(birthday);
    return (T) this;
  }

  public T acceptBirthday(Consumer<Date> consumer) {
    if(this.birthday != null){ 
        consumer.accept(this.birthday.getValue());
    }
    return (T) this;
  }

  public T enable(Boolean enable) {
    this.enable = DataOptional.of(enable);
    return (T) this;
  }

  public T acceptEnable(Consumer<Boolean> consumer) {
    if(this.enable != null){ 
        consumer.accept(this.enable.getValue());
    }
    return (T) this;
  }

  public T name(String name) {
    this.name = DataOptional.of(name);
    return (T) this;
  }

  public T acceptName(Consumer<String> consumer) {
    if(this.name != null){ 
        consumer.accept(this.name.getValue());
    }
    return (T) this;
  }

  public void accept(Person target) {
    this.acceptBirthday(target::setBirthday);
    this.acceptEnable(target::setEnable);
    this.acceptName(target::setName);
  }
}

該類與 BasePersonCreator 存在一些差別:

  1. 屬性使用 DataOptional<T> 進行包裝;
  2. 每一個屬性提供 T fieldName(FieldType fieldName) 方法,用於設置對應的屬性值;
  3. 每一個屬性提供 T acceptFieldName(Consumer<FieldType> consumer) 方法,在 DataOptional 屬性不爲空的時候,進行業務處理;
  4. 提供 void accept(Target target) 方法,將 BaseXXXXUpdater 中的數據寫回到 Target 對象中。

與 BaseXXXCreator 相似,BaseXXXUpdater 也提供 @GenUpdaterIgnore 註解,對方法進行忽略;也可以使用 @Description 註解生成 Swagger 文檔描述。

備註 GenUpdate 與 GenCreator 最大差異在於,Updater 機制,只會應用於 public 的 setter 方法。所以,對於不須要更新的屬性,可使用 protected 訪問級別,這樣只會在 creator 中存在。

3.3 建立 PersonUpdater 類

建立 PersonUpdater 類繼承 BasePersonUpdater。

public class PersonUpdater extends BasePersonUpdater<PersonUpdater>{
}

3.4 建立 update 方法

爲 Person 類 添加 update 方法

public void update(PersonUpdater updater){
    updater.accept(this);
}

4. genDto

dto 是你們最熟悉的模式,但這裏的 dto,只針對返回數據。請求數據,統一使用 Creator 和 Updater 完成。

4.1 啓用 GenDto

爲 Person 類添加 @GenDto 註解。

@GenUpdater
@GenCreator
@GenDto
@Data
public class Person extends JpaAggregate {
    @Description("名稱")
    private String name;
    @Setter(AccessLevel.PROTECTED)
    private Date birthday;
    private Boolean enable;

    public static Person create(PersonCreator creator){
        Person person = new Person();
        creator.accept(person);
        return person;
    }

    public void update(PersonUpdater updater){
        updater.accept(this);
    }
}

4.2 編譯代碼,生成 Base 類

執行 mvn clean compile 生成 BasePersonDto,以下:

@Data
public abstract class BasePersonDto extends AbstractAggregateDto implements Serializable {
  @Setter(AccessLevel.PACKAGE)
  @Getter(AccessLevel.PUBLIC)
  @ApiModelProperty(
      value = "",
      name = "birthday"
  )
  private Date birthday;

  @Setter(AccessLevel.PACKAGE)
  @Getter(AccessLevel.PUBLIC)
  @ApiModelProperty(
      value = "",
      name = "enable"
  )
  private Boolean enable;

  @Setter(AccessLevel.PACKAGE)
  @Getter(AccessLevel.PUBLIC)
  @ApiModelProperty(
      value = "名稱",
      name = "name"
  )
  private String name;

  protected BasePersonDto(Person source) {
    super(source);
    this.setBirthday(source.getBirthday());
    this.setEnable(source.getEnable());
    this.setName(source.getName());
  }
}

4.3 新建 PersonDto

新建 PersonDto 繼承自 BasePersonDto。

public class PersonDto extends BasePersonDto{
    public PersonDto(Person source) {
        super(source);
    }
}

4.3 @GenDto 生成策略

@GenDto 生成策略以下:

  1. 查找類全部的 public getter 方法;
  2. 爲每一個 getter 方法添加屬性;
  3. 新建構造函數,在構造函數中完成目標對象到 BaseXXXDto 的屬性賦值。

5. genConverter

converter 主要針對使用 Jpa 做爲存儲的場景。

5.1 設計背景

Jpa 對 enum 類型提供了兩種存儲方式:

  1. 存儲 enum 的名稱;
  2. 存儲 enum 的定義順序。

這二者在使用上都存在必定的問題,一般狀況下,須要存儲自定義 code,所以,須要實現枚舉類型的 Converter。

5.2 @GenCodeBasedEnumConverter

5.2.1 啓用 GenCodeBasedEnumConverter

新建 PersonStatus 枚舉,實現 CodeBasedEnum 接口,添加 @GenCodeBasedEnumConverter 註解。

@GenCodeBasedEnumConverter
public enum PersonStatus implements CodeBasedEnum<PersonStatus> {
    ENABLE(1), DISABLE(0);

    private final int code;

    PersonStatus(int code) {
        this.code = code;
    }

    @Override
    public int getCode() {
        return this.code;
    }
}
5.2.2 編譯代碼,生成 CodeBasedPersonStatusConverter

執行 mvn clean compile 命令,自動生成 CodeBasedPersonStatusConverter 類

public final class CodeBasedPersonStatusConverter implements AttributeConverter<PersonStatus, Integer> {
  public Integer convertToDatabaseColumn(PersonStatus i) {
    return i == null ? null : i.getCode();
  }

  public PersonStatus convertToEntityAttribute(Integer i) {
    if (i == null) return null;
    for (PersonStatus value : PersonStatus.values()){
        if (value.getCode() == i){
            return value; 
        }
    }
    return null;
  }
}
5.2.3 應用 CodeBasedPersonStatusConverter

在 Person 中使用 CodeBasedPersonStatusConverter 轉化器:

public class Person extends JpaAggregate {
    @Description("名稱")
    private String name;
    @Setter(AccessLevel.PROTECTED)
    private Date birthday;
    private Boolean enable;

    @Convert(converter = CodeBasedPersonStatusConverter.class)
    private PersonStatus status;
}

6. genRepository

Repository 是領域驅動設計中很重要的一個組件,一個聚合根對於一個 Repository。

Repository 與基礎設施關聯緊密,框架經過 @GenSpringDataRepository 提供了 Spring Data Repository 的支持。

6.1 啓用 GenSpringDataRepository

在 Person 上添加 @GenSpringDataRepository 註解。

@GenSpringDataRepository
@Data
public class Person extends JpaAggregate {
    @Description("名稱")
    private String name;
    @Setter(AccessLevel.PROTECTED)
    private Date birthday;
    private Boolean enable;
    @Convert(converter = CodeBasedPersonStatusConverter.class)
    private PersonStatus status;
}

6.2 編譯代碼,生成 Base 類

執行 mvn clean compile 生成 BasePersonRepository 類

interface BasePersonRepository extends AggregateRepository<Long, Person>, Repository<Person, Long>, QuerydslPredicateExecutor<Person> {
}

該接口實現了 AggregateRepository<Long, Person>、Repository<Person, Long>、QuerydslPredicateExecutor<Person> 三個接口,其中 AggregateRepository 爲 ddd-lite 框架接口,另外兩個爲 spring data 接口。

6.3 建立 PersonRepository

建立 PersonRepository 繼承自 BasePersonRepository。

public interface PersonRepository extends BasePersonRepository{
}

6.4 使用 PersonRepository

PersonRepository 爲 Spring Data 標準定義接口,Spring Data 會爲其自動建立代理類,無需咱們實現即可以直接注入使用。

6.5 Index 支持

通常狀況下,PersonRepository 中的方法可以知足咱們大多數需求,若是存在關聯關係,可使用 @Index 處理。

  1. 在 Person 中,添加 @Index({"name", "status"}) 和 @QueryEntity 註解
@GenSpringDataRepository
@Index({"name", "status"})
@QueryEntity

@Data
public class Person extends JpaAggregate {
    @Description("名稱")
    private String name;
    @Setter(AccessLevel.PROTECTED)
    private Date birthday;
    private Boolean enable;
    @Convert(converter = CodeBasedPersonStatusConverter.class)
    private PersonStatus status;
}
  1. 執行 mvn clean compile,查看生成的 PersonRepository
interface BasePersonRepository extends AggregateRepository<Long, Person>, Repository<Person, Long>, QuerydslPredicateExecutor<Person> {
  Long countByName(String name);

  default Long countByName(String name, Predicate predicate) {
    BooleanBuilder booleanBuilder = new BooleanBuilder();
    booleanBuilder.and(QPerson.person.name.eq(name));;
    booleanBuilder.and(predicate);
    return this.count(booleanBuilder.getValue());
  }

  Long countByNameAndStatus(String name, PersonStatus status);

  default Long countByNameAndStatus(String name, PersonStatus status, Predicate predicate) {
    BooleanBuilder booleanBuilder = new BooleanBuilder();
    booleanBuilder.and(QPerson.person.name.eq(name));;
    booleanBuilder.and(QPerson.person.status.eq(status));;
    booleanBuilder.and(predicate);
    return this.count(booleanBuilder.getValue());
  }

  List<Person> getByName(String name);

  List<Person> getByName(String name, Sort sort);

  default List<Person> getByName(String name, Predicate predicate) {
    BooleanBuilder booleanBuilder = new BooleanBuilder();
    booleanBuilder.and(QPerson.person.name.eq(name));;
    booleanBuilder.and(predicate);
    return Lists.newArrayList(findAll(booleanBuilder.getValue()));
  }

  default List<Person> getByName(String name, Predicate predicate, Sort sort) {
    BooleanBuilder booleanBuilder = new BooleanBuilder();
    booleanBuilder.and(QPerson.person.name.eq(name));;
    booleanBuilder.and(predicate);
    return Lists.newArrayList(findAll(booleanBuilder.getValue(), sort));
  }

  List<Person> getByNameAndStatus(String name, PersonStatus status);

  List<Person> getByNameAndStatus(String name, PersonStatus status, Sort sort);

  default List<Person> getByNameAndStatus(String name, PersonStatus status, Predicate predicate) {
    BooleanBuilder booleanBuilder = new BooleanBuilder();
    booleanBuilder.and(QPerson.person.name.eq(name));;
    booleanBuilder.and(QPerson.person.status.eq(status));;
    booleanBuilder.and(predicate);
    return Lists.newArrayList(findAll(booleanBuilder.getValue()));
  }

  default List<Person> getByNameAndStatus(String name, PersonStatus status, Predicate predicate,
      Sort sort) {
    BooleanBuilder booleanBuilder = new BooleanBuilder();
    booleanBuilder.and(QPerson.person.name.eq(name));;
    booleanBuilder.and(QPerson.person.status.eq(status));;
    booleanBuilder.and(predicate);
    return Lists.newArrayList(findAll(booleanBuilder.getValue(), sort));
  }

  Page<Person> findByName(String name, Pageable pageable);

  default Page<Person> findByName(String name, Predicate predicate, Pageable pageable) {
    BooleanBuilder booleanBuilder = new BooleanBuilder();
    booleanBuilder.and(QPerson.person.name.eq(name));;
    booleanBuilder.and(predicate);
    return findAll(booleanBuilder.getValue(), pageable);
  }

  Page<Person> findByNameAndStatus(String name, PersonStatus status, Pageable pageable);

  default Page<Person> findByNameAndStatus(String name, PersonStatus status, Predicate predicate,
      Pageable pageable) {
    BooleanBuilder booleanBuilder = new BooleanBuilder();
    booleanBuilder.and(QPerson.person.name.eq(name));;
    booleanBuilder.and(QPerson.person.status.eq(status));;
    booleanBuilder.and(predicate);
    return findAll(booleanBuilder.getValue(), pageable);
  }
}

根據索引信息,BasePersonRepository 自動生成了各類查詢,這些查詢也不用咱們本身去實現,直接使用便可。

7. GenApplication

application 是領域模型最直接的使用者。

一般狀況下,Application 會涵蓋聚合根中的 Command 方法、Repository 中的查詢方法、DomainService 的操做方法。其中又以聚合根中的 Command 方法和 Repository 中的查詢方法爲主。框架爲此提供了自動生成 BaseXXXApplication 的支持。

框架提供 @GenApplication 註解,做爲自動生成的入口。

7.1 聚合根的 Command

將 @GenApplication 添加到聚合根上,框架自動識別 Command 的方法,並將其添加到 BaseApplication 中。
7.1.1 啓用 GenApplication

在 Person 中添加 @GenApplication 註解,爲了更好的演示 Command 方法,新增 enable 和 disable 兩個方法:

@GenApplication
@Data
public class Person extends JpaAggregate {
    @Description("名稱")
    private String name;
    @Setter(AccessLevel.PROTECTED)
    private Date birthday;
    private Boolean enable;

    @Convert(converter = CodeBasedPersonStatusConverter.class)
    private PersonStatus status;

    public static Person create(PersonCreator creator){
        Person person = new Person();
        creator.accept(person);
        return person;
    }

    public void update(PersonUpdater updater){
        updater.accept(this);
    }

    public void enable(){
        setStatus(PersonStatus.ENABLE);
    }

    public void disable(){
        setStatus(PersonStatus.DISABLE);
    }
}
7.1.2 編譯代碼,生成 Base 類

執行 mvn clean compile,生成 BasePersonApplication 接口和 BasePersonApplicationSupport 實現類。

BasePersonApplication 以下:

public interface BasePersonApplication {
  Long create(PersonCreator creator);

  void disable(@Description("主鍵") Long id);

  void update(@Description("主鍵") Long id, PersonUpdater updater);

  void enable(@Description("主鍵") Long id);
}

BasePersonApplication 主要作了以下工做:

  1. 對於 Person 的 create 靜態工廠方法,將自動建立 create 方法;
  2. 對於 Person 的返回爲 void 方法(非 setter 方法),將自建立爲 command 方法,爲其增長一個主鍵參數。

BasePersonApplicationSupport 以下:

abstract class BasePersonApplicationSupport extends AbstractApplication implements BasePersonApplication {
  @Autowired
  private DomainEventBus domainEventBus;

  @Autowired
  private PersonRepository personRepository;

  protected BasePersonApplicationSupport(Logger logger) {
    super(logger);
  }

  protected BasePersonApplicationSupport() {
  }

  protected PersonRepository getPersonRepository() {
    return this.personRepository;
  }

  protected DomainEventBus getDomainEventBus() {
    return this.domainEventBus;
  }

  @Transactional
  public Long create(PersonCreator creator) {
        Person result = creatorFor(this.getPersonRepository())
                .publishBy(getDomainEventBus())
                .instance(() -> Person.create(creator))
                .call(); 
    logger().info("success to create {} using parm {}",result.getId(), creator);
    return result.getId();
  }

  @Transactional
  public void disable(@Description("主鍵") Long id) {
        Person result = updaterFor(this.getPersonRepository())
                .publishBy(getDomainEventBus())
                .id(id)
                .update(agg -> agg.disable())
                .call(); 
    logger().info("success to disable for {} using parm ", id);
  }

  @Transactional
  public void update(@Description("主鍵") Long id, PersonUpdater updater) {
        Person result = updaterFor(this.getPersonRepository())
                .publishBy(getDomainEventBus())
                .id(id)
                .update(agg -> agg.update(updater))
                .call(); 
    logger().info("success to update for {} using parm {}", id, updater);
  }

  @Transactional
  public void enable(@Description("主鍵") Long id) {
        Person result = updaterFor(this.getPersonRepository())
                .publishBy(getDomainEventBus())
                .id(id)
                .update(agg -> agg.enable())
                .call(); 
    logger().info("success to enable for {} using parm ", id);
  }
}

BasePersonApplicationSupport 主要完成工做以下:

  1. 自動注入 DomainEventBus 和 PersonRepository 等相關資源;
  2. 實現聚合根中的 Command 方法,併爲其開啓事務支持。
7.1.3 建立 PersonApplication 和 PersonApplicationImpl

建立 PersonApplication 和 PersonApplicationImpl 類,具體以下:

PersonApplication:

public interface PersonApplication extends BasePersonApplication{
}

PersonApplicationImpl:

@Service
public class PersonApplicationImpl extends BasePersonApplicationSupport 
        implements PersonApplication {
}

7.2 Repository 中的 Query

將 @GenApplication 添加到 Repository 上,框架將當前接口中的方法做爲 Query 方法,自動添加到 Application 中。
7.2.1 啓用 GenApplication

在 PersonRepository 添加 @GenApplication 註解;

@GenApplication
public interface PersonRepository extends BasePersonRepository{
    
}
7.2.2 添加查詢方法

在 PersonRepository 中添加要暴露的方法:

@GenApplication
public interface PersonRepository extends BasePersonRepository{
    @Override
    Page<Person> findByName(String name, Pageable pageable);

    @Override
    Page<Person> findByNameAndStatus(String name, PersonStatus status, Pageable pageable);
}
7.2.3 編譯代碼,生成 Base 類

執行 mvn clean compile 查看如今的 BasePersonApplication 和 BasePersonApplicationSupport。

BasePersonApplication:

public interface BasePersonApplication {
  Long create(PersonCreator creator);

  void update(@Description("主鍵") Long id, PersonUpdater updater);

  void enable(@Description("主鍵") Long id);

  void disable(@Description("主鍵") Long id);

  Page<PersonDto> findByName(String name, Pageable pageable);

  Page<PersonDto> findByNameAndStatus(String name, PersonStatus status, Pageable pageable);
}

可見,查詢方法已經添加到 BasePersonApplication 中。

BasePersonApplicationSupport:

abstract class BasePersonApplicationSupport extends AbstractApplication implements BasePersonApplication {
  @Autowired
  private DomainEventBus domainEventBus;

  @Autowired
  private PersonRepository personRepository;

  protected BasePersonApplicationSupport(Logger logger) {
    super(logger);
  }

  protected BasePersonApplicationSupport() {
  }

  protected PersonRepository getPersonRepository() {
    return this.personRepository;
  }

  protected DomainEventBus getDomainEventBus() {
    return this.domainEventBus;
  }

  @Transactional
  public Long create(PersonCreator creator) {
        Person result = creatorFor(this.getPersonRepository())
                .publishBy(getDomainEventBus())
                .instance(() -> Person.create(creator))
                .call(); 
    logger().info("success to create {} using parm {}",result.getId(), creator);
    return result.getId();
  }

  @Transactional
  public void update(@Description("主鍵") Long id, PersonUpdater updater) {
        Person result = updaterFor(this.getPersonRepository())
                .publishBy(getDomainEventBus())
                .id(id)
                .update(agg -> agg.update(updater))
                .call(); 
    logger().info("success to update for {} using parm {}", id, updater);
  }

  @Transactional
  public void enable(@Description("主鍵") Long id) {
        Person result = updaterFor(this.getPersonRepository())
                .publishBy(getDomainEventBus())
                .id(id)
                .update(agg -> agg.enable())
                .call(); 
    logger().info("success to enable for {} using parm ", id);
  }

  @Transactional
  public void disable(@Description("主鍵") Long id) {
        Person result = updaterFor(this.getPersonRepository())
                .publishBy(getDomainEventBus())
                .id(id)
                .update(agg -> agg.disable())
                .call(); 
    logger().info("success to disable for {} using parm ", id);
  }

  protected <T> List<T> convertPersonList(List<Person> src, Function<Person, T> converter) {
    if (CollectionUtils.isEmpty(src)) return Collections.emptyList();
    return src.stream().map(converter).collect(Collectors.toList());
  }

  protected <T> Page<T> convvertPersonPage(Page<Person> src, Function<Person, T> converter) {
    return src.map(converter);
  }

  protected abstract PersonDto convertPerson(Person src);

  protected List<PersonDto> convertPersonList(List<Person> src) {
    return convertPersonList(src, this::convertPerson);
  }

  protected Page<PersonDto> convvertPersonPage(Page<Person> src) {
    return convvertPersonPage(src, this::convertPerson);
  }

  @Transactional(
      readOnly = true
  )
  public <T> Page<T> findByName(String name, Pageable pageable, Function<Person, T> converter) {
    Page<Person> result = this.getPersonRepository().findByName(name, pageable);
    return convvertPersonPage(result, converter);
  }

  @Transactional(
      readOnly = true
  )
  public Page<PersonDto> findByName(String name, Pageable pageable) {
    Page<Person> result = this.getPersonRepository().findByName(name, pageable);
    return convvertPersonPage(result);
  }

  @Transactional(
      readOnly = true
  )
  public <T> Page<T> findByNameAndStatus(String name, PersonStatus status, Pageable pageable,
      Function<Person, T> converter) {
    Page<Person> result = this.getPersonRepository().findByNameAndStatus(name, status, pageable);
    return convvertPersonPage(result, converter);
  }

  @Transactional(
      readOnly = true
  )
  public Page<PersonDto> findByNameAndStatus(String name, PersonStatus status, Pageable pageable) {
    Page<Person> result = this.getPersonRepository().findByNameAndStatus(name, status, pageable);
    return convvertPersonPage(result);
  }
}

與上個版本相比,新增如下邏輯:

  1. 添加 convertPersonList、convertPersonPage等轉化方法;
  2. 添加 convertPerson 抽象方法,用於完成 Person 到 PersonDto 的轉化;
  3. 添加 findByNameAndStatus 和 findByName 相關查詢方法,並將其標準爲只讀。
7.2.4 調整 PersonApplicationImpl

爲 PersonApplicationImpl 添加 convertPerson 實現。

@Service
public class PersonApplicationImpl extends BasePersonApplicationSupport
        implements PersonApplication {
    @Override
    protected PersonDto convertPerson(Person src) {
        return new PersonDto(src);
    }
}

至此,對領域的支持就介紹完了,咱們看下咱們的 Person 類。

@GenUpdater
@GenCreator
@GenDto
@GenSpringDataRepository
@Index({"name", "status"})
@QueryEntity

@GenApplication

@Data
public class Person extends JpaAggregate {
    @Description("名稱")
    private String name;
    @Setter(AccessLevel.PROTECTED)
    private Date birthday;
    private Boolean enable;

    @Convert(converter = CodeBasedPersonStatusConverter.class)
    private PersonStatus status;

    public static Person create(PersonCreator creator){
        Person person = new Person();
        creator.accept(person);
        return person;
    }

    public void update(PersonUpdater updater){
        updater.accept(this);
    }

    public void enable(){
        setStatus(PersonStatus.ENABLE);
    }

    public void disable(){
        setStatus(PersonStatus.DISABLE);
    }
}

在 Person 上有一堆的 @GenXXXX,感受有點氾濫,對此,框架提供了兩個符合註解,針對聚合和實體進行優化。

8. EnableGenForEntity

統一開啓實體相關的代碼生成器。

@EnableGenForEntity 等同於同時開啓以下註解:

註解 含義
@GenCreator 自動生成 BaseXXXCreator
@GenDto 自動生成 BaseXXXXDto
@GenUpdater 自動生成 BaseXXXXUpdater

9. EnableGenForAggregate

統一開啓聚合相關的代碼生成器。

@EnableGenForAggregate 等同於同時開啓以下註解:

註解 含義
@GenCreator 自動生成 BaseXXXCreator
@GenDto 自動生成 BaseXXXXDto
@GenUpdater 自動生成 BaseXXXXUpdater
GenSpringDataRepository 自動生成基於 Spring Data 的 BaseXXXRepository
@GenApplication 自動生成 BaseXXXXApplication 以及實現類 BaseXXXXXApplicationSupport
對於領域對象的支持,已經很是完成,那對於 Application 的調用者呢?

框架提供了對於 Controller 的支持。

10. GenController

將 @GenController 添加到 Application 接口上,將啓用對 Controller 的支持。

10.1 啓用 GenController

在 PersonApplication 啓用 Controller 支持。在 PersonApplication 接口上添加 @GenController("com.geekhalo.ddd.lite.demo.controller.BasePersonController")

@GenController("com.geekhalo.ddd.lite.demo.controller.BasePersonController")
public interface PersonApplication extends BasePersonApplication{
}

10.2 編譯代碼,生成 Base 類

執行 mvn clean compile,查看生成的 BasePersonController 類:

abstract class BasePersonController {
  @Autowired
  private PersonApplication application;

  protected PersonApplication getApplication() {
    return this.application;
  }

  @ResponseBody
  @ApiOperation(
      value = "",
      nickname = "create"
  )
  @RequestMapping(
      value = "/_create",
      method = RequestMethod.POST
  )
  public ResultVo<Long> create(@RequestBody PersonCreator creator) {
    return ResultVo.success(this.getApplication().create(creator));
  }

  @ResponseBody
  @ApiOperation(
      value = "",
      nickname = "disable"
  )
  @RequestMapping(
      value = "{id}/_disable",
      method = RequestMethod.POST
  )
  public ResultVo<Void> disable(@PathVariable("id") Long id) {
    this.getApplication().disable(id);
    return ResultVo.success(null);
  }

  @ResponseBody
  @ApiOperation(
      value = "",
      nickname = "update"
  )
  @RequestMapping(
      value = "{id}/_update",
      method = RequestMethod.POST
  )
  public ResultVo<Void> update(@PathVariable("id") Long id, @RequestBody PersonUpdater updater) {
    this.getApplication().update(id, updater);
    return ResultVo.success(null);
  }

  @ResponseBody
  @ApiOperation(
      value = "",
      nickname = "enable"
  )
  @RequestMapping(
      value = "{id}/_enable",
      method = RequestMethod.POST
  )
  public ResultVo<Void> enable(@PathVariable("id") Long id) {
    this.getApplication().enable(id);
    return ResultVo.success(null);
  }

  @ResponseBody
  @ApiOperation(
      value = "",
      nickname = "findByNameAndStatus"
  )
  @RequestMapping(
      value = "/_find_by_name_and_status",
      method = RequestMethod.POST
  )
  public ResultVo<PageVo<PersonDto>> findByNameAndStatus(@RequestBody FindByNameAndStatusReq req) {
    return ResultVo.success(PageVo.apply(this.getApplication().findByNameAndStatus(req.getName(), req.getStatus(), req.getPageable())));
  }

  @ResponseBody
  @ApiOperation(
      value = "",
      nickname = "findByName"
  )
  @RequestMapping(
      value = "/_find_by_name",
      method = RequestMethod.POST
  )
  public ResultVo<PageVo<PersonDto>> findByName(@RequestBody FindByNameReq req) {
    return ResultVo.success(PageVo.apply(this.getApplication().findByName(req.getName(), req.getPageable())));
  }
}

該類主要完成:

  1. 自動注入 PersonApplication;
  2. 爲 Command 中的 create 方法建立對於方法,並返回建立後的主鍵;
  3. 爲 Command 中的 update 方法建立對於方法,在 path 中添加主鍵參數,並返回 Void;
  4. 爲 Query 方法建立對於的方法;
  5. 統一使用 ResultVo 做爲返回值;
  6. 對 Spring Data 中的 Pageable 和 Page 進行封裝;
  7. 對於多參數方法,建立封裝類,使用封裝類收集數據;
  8. 添加 Swagger 相關注解。

10.3 新建 PersonController

新建 PersonController 實現 BasePersonController。並添加 RequestMapping,設置 base path。

@RequestMapping("person")
@RestController
public class PersonController extends BasePersonController{
}

10.4 啓動,查看 Swagger 文檔

至此,Controller 就開發好了,啓動項目,輸入 http://127.0.0.1:8080/swagger-ui.html 即可以看到相關接口。

image

相關文章
相關標籤/搜索