领域驱动设计,构建简单的新闻系统,20分钟够吗?

栏目: 数据库 · 发布时间: 5年前

内容简介:让我们使用领域驱动的方式,构建一个简单的系统。新闻系统的需求如下:大家觉得,针对上面需求,大概需要多长时间可以完成,可以先写下来。

让我们使用领域驱动的方式,构建一个简单的系统。

1. 需求

新闻系统的需求如下:

  1. 创建新闻类别;
  2. 修改新闻类别,只能更改名称;
  3. 禁用新闻类别,禁用后的类别不能添加新闻;
  4. 启用新闻类别;
  5. 根据类别id获取类别信息;
  6. 指定新闻类别id,创建新闻;
  7. 更改新闻信息,只能更改标题和内容;
  8. 禁用新闻;
  9. 启用新闻;
  10. 分页查找给定类别的新闻,禁用的新闻不可见。

2. 工期估算

大家觉得,针对上面需求,大概需要多长时间可以完成,可以先写下来。

3. 起航

3.1. 项目准备

构建项目,使用 http://start.spring.io 或使用模板工程,构建我们的项目(Sprin Boot 项目),在这就不多叙述。

3.1.1. 添加依赖

首先,添加 gh-ddd-lite 相关依赖和插件。

<?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>
        <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>
        <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>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.hibernate.javax.persistence</groupId>
            <artifactId>hibernate-jpa-2.1-api</artifactId>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
        </dependency>
        <dependency>
            <groupId>org.flywaydb</groupId>
            <artifactId>flyway-core</artifactId>
        </dependency>
        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>javax.servlet-api</artifactId>
            <scope>provided</scope>
        </dependency>
        <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>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <executions>
                    <execution>
                        <goals>
                            <goal>repackage</goal>
                        </goals>
                    </execution>
                </executions>
                <configuration>
                    <executable>true</executable>
                    <layout>ZIP</layout>
                </configuration>
            </plugin>
            <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>
                            <processor>com.querydsl.apt.jpa.JPAAnnotationProcessor</processor>
                            <!--<processor>com.querydsl.apt.QuerydslAnnotationProcessor</processor>-->
                        </configuration>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>

</project>

3.1.2. 添加配置信息

在 application.properties 文件中添加数据库相关配置。

spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://127.0.0.1:3306/db_test?useUnicode=true&characterEncoding=utf8&useSSL=false
spring.datasource.username=root
spring.datasource.password=
spring.application.name=ddd-lite-demo
server.port=8090
management.endpoint.beans.enabled=true
management.endpoint.conditions.enabled=true
management.endpoints.enabled-by-default=false
management.endpoints.web.exposure.include=beans,conditions,env

3.1.3. 添加入口类

新建 UserApplication 作为应用入口类。

@SpringBootApplication
@EnableSwagger2
public class UserApplication {
    public static void main(String... args){
        SpringApplication.run(UserApplication.class, args);
    }
}

使用 SpringBootApplication 和 EnableSwagger2 启用 Spring Boot 和 Swagger 特性。

3.2. NewsCategory 建模

首先,我们对新闻类型进行建模。

3.2.1. 建模 NewsCategory 状态

新闻类别状态,用于描述启用、禁用两个状态。在这使用 enum 实现。

/**
 * GenCodeBasedEnumConverter 自动生成 CodeBasedNewsCategoryStatusConverter 类
 */
@GenCodeBasedEnumConverter
public enum  NewsCategoryStatus implements CodeBasedEnum<NewsCategoryStatus> {
    ENABLE(1),
    DISABLE(0);

    private final int code;

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

    @Override
    public int getCode() {
        return code;
    }
}

3.2.2. 建模 NewsCategory

NewsCategory 用于描述新闻类别,其中包括状态、名称等。

3.2.2.1. 新建 NewsCategory

/**
 * EnableGenForAggregate 自动创建聚合相关的 Base 类
 */
@EnableGenForAggregate

@Data
@Entity
@Table(name = "tb_news_category")
public class NewsCategory extends JpaAggregate {

    private String name;

    @Setter(AccessLevel.PRIVATE)
    @Convert(converter = CodeBasedNewsCategoryStatusConverter.class)
    private NewsCategoryStatus status;
}

3.2.2.2. 自动生成 Base 代码

在命令行或ida中执行maven命令,以对项目进行编译,从而触发代码的自动生成。

mvn clean compile

3.2.2.3. 建模 NewsCategory 创建逻辑

我们使用 NewsCategory 的静态工厂,完成其创建逻辑。

首先,需要创建 NewsCategoryCreator,作为工程参数。

public class NewsCategoryCreator extends BaseNewsCategoryCreator<NewsCategoryCreator>{
}

其中 BaseNewsCategoryCreator 为框架自动生成的,具体如下:

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

  public void accept(NewsCategory target) {
    target.setName(getName());
  }
}

接下来,需要创建静态工程,并完成 NewsCategory 的初始化。

/**
 * 静态工程,完成 NewsCategory 的创建
 * @param creator
 * @return
 */
public static NewsCategory create(NewsCategoryCreator creator){
    NewsCategory category = new NewsCategory();
    creator.accept(category);
    category.init();
    return category;
}
/**
 * 初始化,默认状态位 ENABLE
 */
private void init() {
    setStatus(NewsCategoryStatus.ENABLE);
}

3.2.2.4. 建模 NewsCategory 更新逻辑

更新逻辑,只对 name 进行更新操作。

首先,创建 NewsCategoryUpdater 作为,更新方法的参数。

public class NewsCategoryUpdater extends BaseNewsCategoryUpdater<NewsCategoryUpdater>{
}

同样,BaseNewsCategoryUpdater 也是框架自动生成,具体如下:

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

  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(NewsCategory target) {
    this.acceptName(target::setName);
  }
}

添加 update 方法:

/**
 * 更新
 * @param updater
 */
public void update(NewsCategoryUpdater updater){
    updater.accept(this);
}

3.2.2.5. 建模 NewsCategory 启用逻辑

启用,主要是对 status 的操作.

代码如下:

/**
 * 启用
 */
public void enable(){
    setStatus(NewsCategoryStatus.ENABLE);
}

3.2.2.6. 建模 NewsCategory 禁用逻辑

禁用,主要是对 status 的操作。

代码如下:

/**
 * 禁用
 */
public void disable(){
    setStatus(NewsCategoryStatus.DISABLE);
}

至此,NewsCategory 的 Command 就建模完成,让我们总体看下 NewsCategory:

/**
 * EnableGenForAggregate 自动创建聚合相关的 Base 类
 */
@EnableGenForAggregate

@Data
@Entity
@Table(name = "tb_news_category")
public class NewsCategory extends JpaAggregate {

    private String name;

    @Setter(AccessLevel.PRIVATE)
    @Convert(converter = CodeBasedNewsCategoryStatusConverter.class)
    private NewsCategoryStatus status;

    private NewsCategory(){

    }

    /**
     * 静态工程,完成 NewsCategory 的创建
     * @param creator
     * @return
     */
    public static NewsCategory create(NewsCategoryCreator creator){
        NewsCategory category = new NewsCategory();
        creator.accept(category);
        category.init();
        return category;
    }

    /**
     * 更新
     * @param updater
     */
    public void update(NewsCategoryUpdater updater){
        updater.accept(this);
    }

    /**
     * 启用
     */
    public void enable(){
        setStatus(NewsCategoryStatus.ENABLE);
    }

    /**
     * 禁用
     */
    public void disable(){
        setStatus(NewsCategoryStatus.DISABLE);
    }

    /**
     * 初始化,默认状态位 ENABLE
     */
    private void init() {
        setStatus(NewsCategoryStatus.ENABLE);
    }
}

3.2.2.7. 建模 NewsCategory 查找逻辑

查找逻辑主要由 NewsCategoryRepository 完成。

新建 NewsCategoryRepository,如下:

/**
 * GenApplication 自动将该接口中的方法添加到 BaseNewsCategoryRepository 中
 */
@GenApplication
public interface NewsCategoryRepository extends BaseNewsCategoryRepository{
    @Override
    Optional<NewsCategory> getById(Long aLong);
}

同样, BaseNewsCategoryRepository 也是自动生成的。

interface BaseNewsCategoryRepository extends SpringDataRepositoryAdapter<Long, NewsCategory>, Repository<NewsCategory, Long>, QuerydslPredicateExecutor<NewsCategory> {
}

领域对象 NewsCategory 不应该暴露到其他层,因此,我们使用 DTO 模式处理数据的返回,新建 NewsCategoryDto,具体如下:

public class NewsCategoryDto extends BaseNewsCategoryDto{
    public NewsCategoryDto(NewsCategory source) {
        super(source);
    }
}

BaseNewsCategoryDto 为框架自动生成,如下:

@Data
public abstract class BaseNewsCategoryDto extends JpaAggregateVo implements Serializable {
  @Setter(AccessLevel.PACKAGE)
  @Getter(AccessLevel.PUBLIC)
  @ApiModelProperty(
      value = "",
      name = "name"
  )
  private String name;

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

  protected BaseNewsCategoryDto(NewsCategory source) {
    super(source);
    this.setName(source.getName());
    this.setStatus(source.getStatus());
  }
}

3.2.3. 构建 NewsCategoryApplication

至此,领域的建模工作已经完成,让我们对 Application 进行构建。

/**
 * GenController 自动将该类中的方法,添加到 BaseNewsCategoryController 中
 */
@GenController("com.geekhalo.ddd.lite.demo.controller.BaseNewsCategoryController")
public interface NewsCategoryApplication extends BaseNewsCategoryApplication{
    @Override
    NewsCategory create(NewsCategoryCreator creator);

    @Override
    void update(Long id, NewsCategoryUpdater updater);

    @Override
    void enable(Long id);

    @Override
    void disable(Long id);

    @Override
    Optional<NewsCategoryDto> getById(Long aLong);
}

自动生成的 BaseNewsCategoryApplication 如下:

public interface BaseNewsCategoryApplication {
  Optional<NewsCategoryDto> getById(Long aLong);

  NewsCategory create(NewsCategoryCreator creator);

  void update(@Description("主键") Long id, NewsCategoryUpdater updater);

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

  void disable(@Description("主键") Long id);
}

得益于我们的 EnableGenForAggregate 和 GenApplication 注解,BaseNewsCategoryApplication 包含我们想要的 Command 和 Query 方法。

接口已经准备好了,接下来,处理实现类,具体如下:

@Service
public class NewsCategoryApplicationImpl extends BaseNewsCategoryApplicationSupport
    implements NewsCategoryApplication {

    @Override
    protected NewsCategoryDto convertNewsCategory(NewsCategory src) {
        return new NewsCategoryDto(src);
    }
}

自动生成的 BaseNewsCategoryApplicationSupport 如下:

abstract class BaseNewsCategoryApplicationSupport extends AbstractApplication implements BaseNewsCategoryApplication {
  @Autowired
  private DomainEventBus domainEventBus;

  @Autowired
  private NewsCategoryRepository newsCategoryRepository;

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

  protected BaseNewsCategoryApplicationSupport() {
  }

  protected NewsCategoryRepository getNewsCategoryRepository() {
    return this.newsCategoryRepository;
  }

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

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

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

  protected abstract NewsCategoryDto convertNewsCategory(NewsCategory src);

  protected List<NewsCategoryDto> convertNewsCategoryList(List<NewsCategory> src) {
    return convertNewsCategoryList(src, this::convertNewsCategory);
  }

  protected Page<NewsCategoryDto> convvertNewsCategoryPage(Page<NewsCategory> src) {
    return convvertNewsCategoryPage(src, this::convertNewsCategory);
  }

  @Transactional(
      readOnly = true
  )
  public <T> Optional<T> getById(Long aLong, Function<NewsCategory, T> converter) {
    Optional<NewsCategory> result = this.getNewsCategoryRepository().getById(aLong);
    return result.map(converter);
  }

  @Transactional(
      readOnly = true
  )
  public Optional<NewsCategoryDto> getById(Long aLong) {
    Optional<NewsCategory> result = this.getNewsCategoryRepository().getById(aLong);
    return result.map(this::convertNewsCategory);
  }

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

  @Transactional
  public void update(@Description("主键") Long id, NewsCategoryUpdater updater) {
        NewsCategory result = updaterFor(this.getNewsCategoryRepository())
                .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) {
        NewsCategory result = updaterFor(this.getNewsCategoryRepository())
                .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) {
        NewsCategory result = updaterFor(this.getNewsCategoryRepository())
                .publishBy(getDomainEventBus())
                .id(id)
                .update(agg -> agg.disable())
                .call(); 
    logger().info("success to disable for {} using parm ", id);
  }
}

该类中包含我们想要的所有实现。

3.2.4. 构建 NewsCategoryController

NewsInfoApplication 构建完成后,新建 NewsCategoryController 将其暴露出去。

新建 NewsCategoryController, 如下:

@RequestMapping("news_category")
@RestController
public class NewsCategoryController extends BaseNewsCategoryController{
}

是的,核心逻辑都在自动生成的 BaseNewsCategoryController 中:

abstract class BaseNewsCategoryController {
  @Autowired
  private NewsCategoryApplication application;

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

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

  @ResponseBody
  @ApiOperation(
      value = "",
      nickname = "update"
  )
  @RequestMapping(
      value = "{id}/_update",
      method = RequestMethod.POST
  )
  public ResultVo<Void> update(@PathVariable("id") Long id,
      @RequestBody NewsCategoryUpdater 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 = "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 = "getById"
  )
  @RequestMapping(
      value = "/{id}",
      method = RequestMethod.GET
  )
  public ResultVo<NewsCategoryDto> getById(@PathVariable Long id) {
    return ResultVo.success(this.getApplication().getById(id).orElse(null));
  }
}

3.2.5. 数据库准备

至此,我们的代码就完全准备好了,现在需要准备建表语句。

使用 Flyway 作为数据库的版本管理,在 resources/db/migration 新建 V1.002__create_news_category.sql 文件,具体如下:

create table tb_news_category
(
    id bigint auto_increment primary key,

    name varchar(32) null,
    status tinyint null,

    create_time bigint not null,
    update_time bigint not null,
    version tinyint not null
);

3.2.6. 测试

至此,我们就完成了 NewsCategory 的开发。

mvn clean spring-boot:run

浏览器中输入 http://127.0.0.1 :8090/swagger-ui.html , 通过 swagger 查看我们的成果。

可以看到如下

领域驱动设计,构建简单的新闻系统,20分钟够吗?

当然,可以使用 swagger 进行简单测试。

3.3. NewsInfo 建模

在 NewsCategory 的建模过程中,我们的主要精力放在了 NewsCategory 对象上,其他部分基本都是框架帮我们生成的。既然框架为我们做了那么多工作,为什么还需要我们新建 NewsCategoryApplication 和 NewsCategoryController呢?

答案,需要为复杂逻辑预留扩展点。

3.3.1. NewsInfo 建模

整个过程,和 NewsCategory 基本一致,在此不在重复,只选择差异点进行说明。

NewsInfo 最终代码如下:

@EnableGenForAggregate

@Index("categoryId")

@Data
@Entity
@Table(name = "tb_news_info")
public class NewsInfo extends JpaAggregate {
    @Column(name = "category_id", updatable = false)
    private Long categoryId;

    @Setter(AccessLevel.PRIVATE)
    @Convert(converter = CodeBasedNewsInfoStatusConverter.class)
    private NewsInfoStatus status;

    private String title;
    private String content;

    private NewsInfo(){

    }

    /**
     * GenApplicationIgnore 创建 BaseNewsInfoApplication 时,忽略该方法,因为 Optional<NewsCategory> category 需要通过 逻辑进行获取
     * @param category
     * @param creator
     * @return
     */
    @GenApplicationIgnore
    public static NewsInfo create(Optional<NewsCategory> category, NewsInfoCreator creator){
        // 对 NewsCategory 的存在性和状态进行验证
        if (!category.isPresent() || category.get().getStatus() != NewsCategoryStatus.ENABLE){
            throw new IllegalArgumentException();
        }
        NewsInfo newsInfo = new NewsInfo();
        creator.accept(newsInfo);
        newsInfo.init();
        return newsInfo;
    }

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

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

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

    private void init() {
        setStatus(NewsInfoStatus.ENABLE);
    }
}

3.3.1.1. NewsInfo 创建逻辑建模

NewsInfo 的创建逻辑中,需要对 NewsCategory 的存在性和状态进行检查,只有存在并且状态为 ENABLE 才能添加 NewsInfo。

具体实现如下:

/**
 * GenApplicationIgnore 创建 BaseNewsInfoApplication 时,忽略该方法,因为 Optional<NewsCategory> category 需要通过 逻辑进行获取
 * @param category
 * @param creator
 * @return
 */
@GenApplicationIgnore
public static NewsInfo create(Optional<NewsCategory> category, NewsInfoCreator creator){
    // 对 NewsCategory 的存在性和状态进行验证
    if (!category.isPresent() || category.get().getStatus() != NewsCategoryStatus.ENABLE){
        throw new IllegalArgumentException();
    }
    NewsInfo newsInfo = new NewsInfo();
    creator.accept(newsInfo);
    newsInfo.init();
    return newsInfo;
}

该方法比较复杂,需要我们手工处理。

在 NewsInfoApplication 中手工添加创建方法:

@GenController("com.geekhalo.ddd.lite.demo.controller.BaseNewsInfoController")
public interface NewsInfoApplication extends BaseNewsInfoApplication{
    // 手工维护方法
    NewsInfo create(Long categoryId, NewsInfoCreator creator);
}

在 NewsInfoApplicationImpl 添加实现:

@Autowired
private NewsCategoryRepository newsCategoryRepository;

@Override
public NewsInfo create(Long categoryId, NewsInfoCreator creator) {
    return creatorFor(getNewsInfoRepository())
            .publishBy(getDomainEventBus())
            .instance(()-> NewsInfo.create(this.newsCategoryRepository.getById(categoryId), creator))
            .call();
}

其他部分不需要调整。

3.3.2. NewsInfo 查找逻辑建模

查找逻辑设计两个部分:

  1. 根据 categoryId 进行分页查找;
  2. 禁用的 NewsInfo 在查找中不可见。

3.3.2.1. Index 注解

在 NewsInfo 类上多了一个 @Index("categoryId") 注解,该注解会在 BaseNewsInfoRepository 中添加以 categoryId 为维度的查询。

interface BaseNewsInfoRepository extends SpringDataRepositoryAdapter<Long, NewsInfo>, Repository<NewsInfo, Long>, QuerydslPredicateExecutor<NewsInfo> {
  Long countByCategoryId(Long categoryId);

  default Long countByCategoryId(Long categoryId, Predicate predicate) {
    BooleanBuilder booleanBuilder = new BooleanBuilder();
    booleanBuilder.and(QNewsInfo.newsInfo.categoryId.eq(categoryId));;
    booleanBuilder.and(predicate);
    return this.count(booleanBuilder.getValue());
  }

  List<NewsInfo> getByCategoryId(Long categoryId);

  List<NewsInfo> getByCategoryId(Long categoryId, Sort sort);

  default List<NewsInfo> getByCategoryId(Long categoryId, Predicate predicate) {
    BooleanBuilder booleanBuilder = new BooleanBuilder();
    booleanBuilder.and(QNewsInfo.newsInfo.categoryId.eq(categoryId));;
    booleanBuilder.and(predicate);
    return Lists.newArrayList(findAll(booleanBuilder.getValue()));
  }

  default List<NewsInfo> getByCategoryId(Long categoryId, Predicate predicate, Sort sort) {
    BooleanBuilder booleanBuilder = new BooleanBuilder();
    booleanBuilder.and(QNewsInfo.newsInfo.categoryId.eq(categoryId));;
    booleanBuilder.and(predicate);
    return Lists.newArrayList(findAll(booleanBuilder.getValue(), sort));
  }

  Page<NewsInfo> findByCategoryId(Long categoryId, Pageable pageable);

  default Page<NewsInfo> findByCategoryId(Long categoryId, Predicate predicate, Pageable pageable) {
    BooleanBuilder booleanBuilder = new BooleanBuilder();
    booleanBuilder.and(QNewsInfo.newsInfo.categoryId.eq(categoryId));;
    booleanBuilder.and(predicate);
    return findAll(booleanBuilder.getValue(), pageable);
  }
}

这样,并解决了第一个问题。

3.3.2.2. 默认方法

查看 NewsInfoRepository 类,如下:

@GenApplication
public interface NewsInfoRepository extends BaseNewsInfoRepository{

    default Page<NewsInfo> findValidByCategoryId(Long categoryId, Pageable pageable){
        // 查找有效状态
        Predicate valid = QNewsInfo.newsInfo.status.eq(NewsInfoStatus.ENABLE);
        return findByCategoryId(categoryId, valid, pageable);
    }
}

通过默认方法将业务概念转为为数据过滤。

3.3.3. NewsInfo 数据库准备

至此,整个结构与 NewsCategory 再无区别。

create table tb_news_info
(
    id bigint auto_increment primary key,

  category_id bigint not null,
    status tinyint null,
    title varchar(64) not null,
    content text null,

    create_time bigint not null,
    update_time bigint not null,
    version tinyint not null
);

3.3.4. NewsInfo 测试

启动项目,进行简单测试。

领域驱动设计,构建简单的新闻系统,20分钟够吗?

4. 总结

你用了多长时间完成整个系统呢?

项目地址见: https://gitee.com/litao851025...


以上所述就是小编给大家介绍的《领域驱动设计,构建简单的新闻系统,20分钟够吗?》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

轻量级Django

轻量级Django

茱莉亚·埃尔曼 (Julia Elman)、马克·拉温 (Mark Lavin) / 侯荣涛、吴磊 / 中国电力出版社; 第1版 / 2016-11-1 / 35.6

自Django 创建以来,各种各样的开源社区已经构建了很多Web 框架,比如JavaScript 社区创建的Angular.js 、Ember.js 和Backbone.js 之类面向前端的Web 框架,它们是现代Web 开发中的先驱。Django 从哪里入手来适应这些框架呢?我们如何将客户端MVC 框架整合成为当前的Django 基础架构? 本书讲述如何利用Django 强大的“自支持”功......一起来看看 《轻量级Django》 这本书的介绍吧!

在线进制转换器
在线进制转换器

各进制数互转换器

MD5 加密
MD5 加密

MD5 加密工具

HEX CMYK 转换工具
HEX CMYK 转换工具

HEX CMYK 互转工具