Skip to main content

应用层实现

应用层的实现,有几个需要特别说明的地方,它们是:

  1. 定义接口的实现
  2. Assembler组装器
  3. QueryChannel查询通道
  4. 应用实现的bean定义方式

1. 定义接口的实现

应用层实现需要实现应用层申明的接口。

所以,第一步是申明接口的实现


public class UserApplicationImpl implements UserApplication {

@Override
@Transactional
public UserDto createLocalUser(UserDto request) {
User created = Objects.requireNonNull(UserAssembler.toUser(request)).createLocalUser();
return UserAssembler.toUserDto(created);
}

//... 省略其它

}

要特别注意,应用层的实现应该是足够简单的,大部分业务都应该是在领域层实现的。如果你应用层的代码逻辑过于复杂,需要仔细检查是不是把一些应该写到领域层的方法写到应用层中来了

在应用层中,会遇到两个频繁使用的概念,它们分别是Assembler组装器以及查询通道

2. Assembler组装器

在应用层中,Assembler组装器是一个非常重要的概念,它是将领域层中的实体对象转换为DTO对象。

这样的原因在于:

  • 领域实体不应该传递出去被引用或使用,这会侵入领域行为,造成混乱,比如会造成在Rest层调用一个领域的方法。
  • 领域实体并非单纯的数据对象,有很多领域方法,在序列化与反序列化时会存在问题,所以我们需要以DTO取代领域实体

因此,在DDD的编码中,应用层通常会有DTO数据对象以及Assembler组装器两种概念

由于本项目用的是protobuf,DTO是由protobuf自动生成的,所以无须额外定义。但Assembler组装器需要自己实现

实现一个Assembler组装器


public class UserAssembler {

public static User toUser(UserDto dto){
if(Objects.isNull(dto))return null;
User user = new User();
if(dto.getId() > 0)user.setId(dto.getId());
user.setUserId(dto.getUserId());
user.setPassword(dto.getPassword());
user.setEncodePassword(dto.getEncodePassword());
user.setName(dto.getName());
user.setEmail(dto.getEmail());
user.setPhone(dto.getPhone());
user.setDisabled(dto.getDisabled());
return user;
}

public static UserDto toUserDto(User user){
if(Objects.isNull(user))return null;
UserDto.Builder builder = UserDto.newBuilder()
.setId(user.getId())
.setUserId(user.getUserId())
.setEncodePassword(user.getEncodePassword())
.setName(user.getName())
.setEmail(user.getEmail())
.setPhone(user.getPhone())
.setDisabled(user.isDisabled());

if(Objects.nonNull(user.getPassword())){
builder.setPassword(user.getPassword());
}

return builder.build();
}

}

如上述代码所示,我们定义了一个组装器,用于实体与DTO数据的转换,这样我们在应用的实现中,可以使用到组装器

3. QueryChannel查询通道

在领域驱动中,我们主张将业务实现放在领域层。

但有一种用例需求,特别是以分页查询为典型代表,其不属于业务核心。也就是业务中实质并不存在分页去查询XX的概念,这种概念更多的属于系统UI展现上来的需求,所以在这里要使用QueryChannel也就是查询通道的概念。

查询通道提供了对分页查询的封装



public class UserApplicationImpl implements UserApplication {

private QueryChannelService queryChannelService;

private QueryChannelService getQueryChannelService(){
if(Objects.isNull(queryChannelService)){
queryChannelService = InstanceFactory.getInstance(QueryChannelService.class);
}
return queryChannelService;
}

@Override
public PageUserDto pageQueryUser(PageQueryDto request) {
Page<User> userPage = getQueryChannelService()
.createJpqlQuery("from User",User.class)
.setPage(request.getPage(),request.getPageSize())
.pagedList();


return PageUserDto.newBuilder()
.setPage(request.getPage())
.setPageSize(request.getPageSize())
.setTotal(userPage.getResultCount())
.addAllUsers(userPage.getData().stream().map(UserAssembler::toUserDto).collect(Collectors.toList()))
.build();
}

//... 省略其它

}

上面的代码是使用查询通道的一个事例。

4. 依赖注入的不同

与其它层有点不同,在其它层中,申明一个类注入,使用的是@Named,而使用一个注入,用的是@Inject。 但由于本项目是同时支持单体构建分布式微服务构建两种模式。 所以应用层中,申请注入的方式并不一样

在应用层的实现类上,不要使用@Named,因为它不支持dubbo

4.1 申明依赖注入

在l7e-rest/l7e-local-strategy模块中,申明单体构建式注入


<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://www.springframework.org/schema/beans" xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
<context:property-placeholder/>

<bean id="userRoleApplication" class="com.foreverht.lowcode.security.application.UserRoleApplicationImpl" />
</beans>

而在l7e-rest/l7e-micro-strategy模块中,申明分布式微服务注入


<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:dubbo="http://dubbo.apache.org/schema/dubbo"
xmlns="http://www.springframework.org/schema/beans" xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://dubbo.apache.org/schema/dubbo http://dubbo.apache.org/schema/dubbo/dubbo.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
<context:property-placeholder/>

<dubbo:application name="rest-micro-consumer"/>

<dubbo:protocol port="-1" name="dubbo"/>
<dubbo:registry address="${nacos.address}" />
<dubbo:provider token="false"/>

<dubbo:reference id="userApplication" check="false" interface="com.foreverht.lowcode.security.api.UserApplication" timeout="30000" retries="1" loadbalance="random" actives="0"/>

</beans>

所以,我们使用的是,同一套代码,不同的注入方式,来支持项目的单体构建与分布式构建两种模式。

4.2 应用实现中使用依赖

因为上述原因,在应用层实现类中,使用任何第三方依赖,都是通过InstanceFactory来获取,而非直接使用 @Inject 来实现,这一点要特别注意


public class UserApplicationImpl implements UserApplication {

private QueryChannelService queryChannelService;

//通过**InstanceFactory**来获取,而非直接使用 **@Inject** 来获取实例
private QueryChannelService getQueryChannelService(){
if(Objects.isNull(queryChannelService)){
queryChannelService = InstanceFactory.getInstance(QueryChannelService.class);
}
return queryChannelService;
}

//... 省略其它
}

5. 单元测试

同样,在实现一个应用接口后,我们对其做单元测试


public class TestUserApplication extends AbstractTest {

@Inject
private UserApplication userApplication;

@Test
void testUserApplicationExist(){
Assertions.assertNotNull(userApplication);
}

@Test
void testCreateUser(){
UserDto created = userApplication.createLocalUser(randomUserDto());
Assertions.assertNotNull(created);

UserDto nameEmptyUserDto = UserDto.newBuilder().build();
Assertions.assertThrows(UserNameEmptyException.class,()->{
userApplication.createLocalUser(nameEmptyUserDto);
});
}
}


要特别注意,想要支持单元测试,需要在src/test/resource下,定义本地bean xml

<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:dubbo="http://dubbo.apache.org/schema/dubbo"
xmlns="http://www.springframework.org/schema/beans" xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://dubbo.apache.org/schema/dubbo http://dubbo.apache.org/schema/dubbo/dubbo.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
<context:property-placeholder/>

<bean id="userApplication" class="com.foreverht.lowcode.security.application.UserApplicationImpl"/>

</beans>

如果不定义,则IOC会找不到UserApplication的实例