ddd-lite-codegen 樣板程式碼終結者
ddd-lite-codegen
基於 ddd lite 和 ddd lite spring 體系構建,基於領域模型物件自動生成其他非核心程式碼。
0. 執行原理
ddd lite codegen 構建於 apt 技術之上。
框架提供若干註解和註解處理器,在編譯階段,自動生成所需的 Base 類。這些 Base 類隨著領域物件的重構而變化,從而大大減少樣板程式碼。
如有特殊需求,可以通過子類進行擴充套件,而無需修改 Base 類的內容(每次編譯,Base 類都會自動生成)。
1. 配置
該框架採用兩步處理,第一步由 maven 的 apt plugin 完成,第二步由編譯器呼叫 apt 元件完成。
對於 maven 專案,需要新增相關依賴和外掛,具體如下:
<?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 中提取所需資料,完成物件建立工作。
creator就是這種特殊的 Dto,在封裝建立物件所需的資料的同時,提供資料到領域物件的繫結操作。
2.1 常規做法
假設,現在有一個 Person 類:
@Data public class Person { private String name; private Date birthday; private Boolean enable; }
我們需要建立新的 Person 物件,比較正統的方式便是,建立一個 PersonCreator,用於封裝所需資料:
@Data public class PersonCreator { private String name; private Date birthday; private Boolean enable; }
然後,在 Person 中新增建立方法,如:
public static Person create(PersonCreator creator){ Person person = new Person(); person.setName(creator.getName()); person.setBirthday(creator.getBirthday()); person.setEnable(creator.getEnable()); return person; }
大家有沒有發現問題:
- Person 和 PersonCreator 包含的屬性基本相同
- 如果在 Person 中新增、移除、修改屬性,會同時調整三處(Person、PersonCreator、create 方法),遺漏任何一處,都會導致邏輯錯誤
對於這種機械而且有規律的場景,是否可以採用自動化方式完成?
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 執行原理如下:
- 自動讀取當前類的 setter 方法;
- 篩選 public 和 protected 訪問級別的 setter 方法,將其作為屬性新增到 BaseXXXCreator 類中;
- 建立 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 存在一些差異:
- 屬性使用 DataOptional<T> 進行包裝;
- 每個屬性提供 T fieldName(FieldType fieldName) 方法,用於設定對應的屬性值;
- 每個屬性提供 T acceptFieldName(Consumer<FieldType> consumer) 方法,在 DataOptional 屬性不為空的時候,進行業務處理;
- 提供 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 生成策略如下:
- 查詢類所有的 public getter 方法;
- 為每個 getter 方法新增屬性;
- 新建建構函式,在建構函式中完成目標物件到 BaseXXXDto 的屬性賦值。
5. genConverter
converter 主要針對使用 Jpa 作為儲存的場景。
5.1 設計背景
Jpa 對 enum 型別提供了兩種儲存方式:
- 儲存 enum 的名稱;
- 儲存 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 處理。
- 在 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; }
- 執行 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 主要做了如下工作:
- 對於 Person 的 create 靜態工廠方法,將自動建立 create 方法;
- 對於 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 主要完成工作如下:
- 自動注入 DomainEventBus 和 PersonRepository 等相關資源;
- 實現聚合根中的 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); } }
與上個版本相比,新增以下邏輯:
- 新增 convertPersonList、convertPersonPage等轉化方法;
- 新增 convertPerson 抽象方法,用於完成 Person 到 PersonDto 的轉化;
- 新增 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()))); } }
該類主要完成:
- 自動注入 PersonApplication;
- 為 Command 中的 create 方法建立對於方法,並返回建立後的主鍵;
- 為 Command 中的 update 方法建立對於方法,在 path 中新增主鍵引數,並返回 Void;
- 為 Query 方法建立對於的方法;
- 統一使用 ResultVo 作為返回值;
- 對 Spring Data 中的 Pageable 和 Page 進行封裝;
- 對於多引數方法,建立封裝類,使用封裝類收集資料;
- 新增 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 便可以看到相關介面。