原文作者:Mariusz Kumor
原文地址:https://dzone.com/articles/ddd-tiers-approach-in-microservices-based-abixen
一个明确的方法对在 IT 项目里给出易读的代码、结构还有逻辑(一般的说法是程序的规则)极其重要。对此,程序员可以使用很多取决于专门的使用场景的模式。不过,如今这个世界已被使用微服务架构编写的系统所支配,而其中的基本原理应该是领域驱动设计(Domain-Driven Design,DDD)。
网上有很多教程仅用一些编程语言写了两三个类,而一个用 Spring 框架编写的完整可用的应用则很难找到。我们会在此介绍 Abixen 平台来作为一个范例。Abixen 平台是一个基于微服务的软件平台,可用于构建提供商业智能和 Web 内容的企业级应用。它的源码可以在 GitHub 上找到。
本文的主旨并不是介绍领域驱动设计的理论基础,而是着眼于一个流行的,在 GitHub 上有个活跃社区的应用(尽管对市场而言还很新)。这里还应该指出一点,这个项目的当前(2018.05.18)版本并没有实现领域驱动设计的所有元素和功能。比如命令查询职责分离(Command Query Responsibility Segregation,CQRS)便是一个计划在将来才实现的功能。不过,本文所给出的内容其实也足够展示一个 DDD 的实现会是怎么样的。这一平台有个好处在于它是用 Java 等流行语言编写的,且其所基于的是我们经常能见到和用到的 Spring Boot。
Abixen 平台划分了三个独立的领域:
由于这项目是一个基于微服务的应用,这就意味着一个领域会被封装在一个微服务里。具体而言,领域之间会在 Spring Cloud 和 Netflix OSS 的基础上进行交互。在此要强调一点,有些涉及技术细节的微服务并没有封装一个领域。
Abixen 平台将微服务划分为四个层级:
下面就是上述各部分的架构图:
接口层是应用程序与外界进行交互的一层。例如一个核心微服务会有两类接口:
Web 接口负责与应用程序的用户进行通信。在该项目中,这部分会以 Spring 控制器的形式实现。比如下面给出的代码便是其中一个控制器 RoleController
:
@Slf4j
@RestController
@RequestMapping(value = "/api/control-panel/roles")
public class RoleController {
private final RoleManagementService roleManagementService;
@Autowired
public RoleController(RoleManagementService roleManagementService) {
this.roleManagementService = roleManagementService;
}
@RequestMapping(value = "", method = RequestMethod.GET)
public Page<RoleDto> findAll(@PageableDefault(size = 1, page = 0) Pageable pageable, RoleSearchForm roleSearchForm) {
log.debug("findAll() - roleSearchForm: {}", roleSearchForm);
return roleManagementService.findAllRoles(pageable, roleSearchForm);
}
@RequestMapping(value = "/{id}", method = RequestMethod.GET)
public RoleDto find(@PathVariable Long id) {
log.debug("find() - id: {}", id);
return roleManagementService.findRole(id);
}
@RequestMapping(value = "", method = RequestMethod.POST)
public FormValidationResultDto<RoleForm> create(@RequestBody @Valid RoleForm roleForm, BindingResult bindingResult) {
log.debug("create() - roleForm: {}", roleForm);
if (bindingResult.hasErrors()) {
List<FormErrorDto> formErrors = ValidationUtil.extractFormErrors(bindingResult);
return new FormValidationResultDto<>(roleForm, formErrors);
}
final RoleForm createdRoleForm = roleManagementService.createRole(roleForm);
return new FormValidationResultDto<>(createdRoleForm);
}
@RequestMapping(value = "/{id}", method = RequestMethod.PUT)
public FormValidationResultDto<RoleForm> update(@PathVariable("id") Long id, @RequestBody @Valid RoleForm roleForm, BindingResult bindingResult) {
log.debug("update() - id: {}, roleForm: {}", id, roleForm);
if (bindingResult.hasErrors()) {
List<FormErrorDto> formErrors = ValidationUtil.extractFormErrors(bindingResult);
return new FormValidationResultDto<>(roleForm, formErrors);
}
final RoleForm updatedRoleForm = roleManagementService.updateRole(roleForm);
return new FormValidationResultDto<>(updatedRoleForm);
}
@RequestMapping(value = "/{id}", method = RequestMethod.DELETE)
public ResponseEntity<Boolean> delete(@PathVariable("id") long id) {
log.debug("delete() - id: {}", id);
roleManagementService.deleteRole(id);
return new ResponseEntity<>(Boolean.TRUE, HttpStatus.OK);
}
@RequestMapping(value = "/{id}/permissions", method = RequestMethod.GET)
public RolePermissionsForm findPermissions(@PathVariable Long id) {
log.debug("findPermissions() - id: {}", id);
return roleManagementService.findRolePermissions(id);
}
@RequestMapping(value = "/{id}/permissions", method = RequestMethod.PUT)
public FormValidationResultDto<RolePermissionsForm> updatePermissions(@PathVariable("id") Long id, @RequestBody @Valid RolePermissionsForm rolePermissionsForm, BindingResult bindingResult) {
log.debug("updatePermissions() - id: {}, rolePermissionsForm: {}", id, rolePermissionsForm);
if (bindingResult.hasErrors()) {
List<FormErrorDto> formErrors = ValidationUtil.extractFormErrors(bindingResult);
return new FormValidationResultDto<>(rolePermissionsForm, formErrors);
}
final RolePermissionsForm updatedRolePermissionsForm = roleManagementService.updateRolePermissions(rolePermissionsForm);
return new FormValidationResultDto<>(updatedRolePermissionsForm);
}
}
RoleController
提供了一套包含 find
、findAll
、create
、update
、delete
等操作的 RESTful API。这里有个重点在于控制器必须在 DTO 类上运行。在此会有个 RoleDto
来充当领域模型的表示。以及会有一个 RoleForm
对象充当命令的具体内容,比如在用户要创建一个角色的时候,就会通过接口将所有用户提供的信息从 Web 前端通过这一表单对象传递到应用层。
@Getter
@Setter
@Accessors(chain = true)
@ToString
@EqualsAndHashCode(of = "name")
public class RoleDto extends AuditingDto {
private Long id;
private RoleType roleType;
private String name;
private Set<PermissionDto> permissions;
}
public class RoleForm implements Form {
private Long id;
@NotNull
@Length(max = Role.ROLE_NAME_MAX_LENGTH)
private String name;
private RoleType roleType;
public RoleForm() {
}
public RoleForm(Role role) {
this.id = role.getId();
this.name = role.getName();
this.roleType = role.getRoleType();
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public RoleType getRoleType() {
return roleType;
}
public void setRoleType(RoleType roleType) {
this.roleType = roleType;
}
}
AMQP 接口是和消息队列之间的联系点。Abixen 平台使用了 RabbitMQ 在特定的微服务之间进行异步通信。
在这之中有一个使用场景是删除模块。核心(Core)微服务会在此充当一个协调器,并在平台的界面中呈现有关已登记的模块实例的根消息(如商业智能、Web 内容或其他由开发人员设计的自创模块)。在用户执行删除模块实例的命令时,Core 会执行一个核心操作,并将这个命令(“我删了我的,你也该删你的了”)发送到商业智能微服务对应的消息队列中。
维护一个接口并编写一个基于消息队列的实现是一个不错的做法,这一消息队列的实现不会干扰到其他层的维护,其他层也不会对系统里面的消息队列有所感知。上述的 Abixen 平台便是基于 Spring Cloud Stream,使用这一模式实现的。下面给出接口 DeleteModuleService
还有其实现类 AmqpDeleteModuleService
的代码:
public interface DeleteModuleService {
void delete(String routingKey, DeleteModuleCommand deleteModuleCommand);
}
@Slf4j
@Service
@EnableBinding(DeleteModuleSource.class)
public class AmqpDeleteModuleService implements DeleteModuleService {
private final DeleteModuleSource deleteModuleSource;
@Autowired
public AmqpDeleteModuleService(DeleteModuleSource deleteModuleSource) {
this.deleteModuleSource = deleteModuleSource;
}
@Override
public void delete(final String routingKey, final DeleteModuleCommand deleteModuleCommand) {
log.info("will send {}", deleteModuleCommand);
try {
final boolean sent = deleteModuleSource.output().send(
MessageBuilder.withPayload(deleteModuleCommand)
.setHeader(SimpMessageHeaderAccessor.DESTINATION_HEADER, routingKey)
.build());
log.info("sent {} {}", sent, deleteModuleCommand);
} catch (final Exception e) {
log.error("Couldn't send command ", e);
}
}
}
使用 Spring Cloud Stream 实现的通信过程如下:
Abixen 平台采用的 DDD 的下一级是应用层。该层主要负责实现来自接口层的请求,是对领域层里面的服务的一种协调器(我们将在本文后面讨论)。应用层不应有复杂的逻辑,而应保持清晰明确。同样重要的是,它在领域对象和接口对象上运行。应用程序层中的关键构件是服务。在 Abixen 平台中,它们以 Spring 框架下的服务的形式实现。下面就是是负责界面管理的 DashboardService
:
@Slf4j
@Transactional
@PlatformApplicationService
public class DashboardService {
private final PageService pageService;
private final LayoutService layoutService;
private final DashboardModuleService dashboardModuleService;
private final PageToPageDtoConverter pageToPageDtoConverter;
private final ModuleToDashboardModuleDtoConverter moduleToDashboardModuleDtoConverter;
@Autowired
public DashboardService(PageService pageService,
LayoutService layoutService,
DashboardModuleService dashboardModuleService,
PageToPageDtoConverter pageToPageDtoConverter,
ModuleToDashboardModuleDtoConverter moduleToDashboardModuleDtoConverter) {
this.pageService = pageService;
this.layoutService = layoutService;
this.dashboardModuleService = dashboardModuleService;
this.pageToPageDtoConverter = pageToPageDtoConverter;
this.moduleToDashboardModuleDtoConverter = moduleToDashboardModuleDtoConverter;
}
public DashboardDto find(final Long pageId) {
log.debug("find() - pageId: {}", pageId);
final Page page = pageService.find(pageId);
final List<Module> modules = dashboardModuleService.findAllModules(page);
final PageDto pageDto = pageToPageDtoConverter.convert(page);
final List<DashboardModuleDto> dashboardModules = moduleToDashboardModuleDtoConverter.convertToList(modules);
return new DashboardDto(pageDto, dashboardModules);
}
public DashboardForm create(final DashboardForm dashboardForm) {
log.debug("create() - dashboardForm: {}", dashboardForm);
final Page page = Page.builder()
.layout(layoutService.find(dashboardForm.getPage().getLayout().getId()))
.title(dashboardForm.getPage().getTitle())
.description(dashboardForm.getPage().getDescription())
.icon(dashboardForm.getPage().getIcon())
.build();
final Page createdPage = pageService.create(page);
final PageDto pageDto = pageToPageDtoConverter.convert(createdPage);
return new DashboardForm(pageDto);
}
public DashboardForm update(final DashboardForm dashboardForm) {
log.debug("update() - dashboardForm: {}", dashboardForm);
return change(dashboardForm, false);
}
public DashboardForm configure(final DashboardForm dashboardForm) {
log.debug("configure() - dashboardForm: {}", dashboardForm);
return change(dashboardForm, true);
}
private DashboardForm change(final DashboardForm dashboardForm, final boolean configurationChangeType) {
final Page page = pageService.find(dashboardForm.getPage().getId());
if (configurationChangeType) {
validateConfiguration(dashboardForm, page);
}
page.changeDescription(dashboardForm.getPage().getDescription());
page.changeTitle(dashboardForm.getPage().getTitle());
page.changeIcon(dashboardForm.getPage().getIcon());
page.changeLayout(layoutService.find(dashboardForm.getPage().getLayout().getId()));
pageService.update(page);
final List<Long> updatedModulesIds = dashboardModuleService.updateExistingModules(dashboardForm.getDashboardModuleDtos());
final List<Long> createdModulesIds = dashboardModuleService.createNotExistingModules(dashboardForm.getDashboardModuleDtos(), page);
final List<Long> currentModulesIds = new ArrayList<>();
currentModulesIds.addAll(updatedModulesIds);
currentModulesIds.addAll(createdModulesIds);
dashboardModuleService.deleteAllModulesExcept(page, currentModulesIds);
return dashboardForm;
}
private void validateConfiguration(final DashboardForm dashboardForm, final Page page) {
boolean validationFailed = false;
if (page.getDescription() == null && dashboardForm.getPage().getDescription() != null) {
validationFailed = true;
} else if (page.getDescription() != null && !page.getDescription().equals(dashboardForm.getPage().getDescription())) {
validationFailed = true;
} else if (!page.getTitle().equals(dashboardForm.getPage().getTitle())) {
validationFailed = true;
}
if (validationFailed) {
throw new PlatformCoreException("Can not modify page's parameters during configuration's update operation.");
}
}
}
上面的代码给出了应用服务的协调其他依赖服务的工作,并使用转换器实现在用户对象和领域对象之间的转换。这一平台引入了转换器模式。下面便是 ModuleToDashboardModuleDtoConverter
的代码,注意它依赖了另外一个转换器:
@Component
public class ModuleToDashboardModuleDtoConverter extends AbstractConverter<Module, DashboardModuleDto> {
private final ModuleTypeToModuleTypeDtoConverter moduleTypeToModuleTypeDtoConverter;
@Autowired
public ModuleToDashboardModuleDtoConverter(ModuleTypeToModuleTypeDtoConverter moduleTypeToModuleTypeDtoConverter) {
this.moduleTypeToModuleTypeDtoConverter = moduleTypeToModuleTypeDtoConverter;
}
@Override
public DashboardModuleDto convert(Module module, Map<String, Object> parameters) {
DashboardModuleDto dashboardModuleDto = new DashboardModuleDto();
dashboardModuleDto
.setId(module.getId())
.setTitle(module.getTitle())
.setDescription(module.getDescription())
.setType(module.getModuleType().getName())
.setModuleType(moduleTypeToModuleTypeDtoConverter.convert(module.getModuleType()))
.setRowIndex(module.getRowIndex())
.setColumnIndex(module.getColumnIndex())
.setOrderIndex(module.getOrderIndex());
return dashboardModuleDto;
}
}
项目中的领域层会负责执行微服务的所有业务操作,可以看作系统的核心。Abixen 平台对这一层划分了这些构件:
领域的模型其实会被其他两个构建所使用,因此我们便从这里开始讨论领域。Abixen 平台给出了一种以透明且易于维护的方式创建模型的方法。目前,这一部分由包含了以下几个构件:
下面便是作为值对象的 AclClass
,以及作为实体的 Role
:
@Entity
@Table(name = "acl_class")
public class AclClass {
/**
*
*/
private static final long serialVersionUID = -3518427281918839763L;
/**
* Represents a canonical name of domain class.
* E.g. com.abixen.platform.core.domain.model.User
*/
@Enumerated(EnumType.STRING)
@Column(name = "name", nullable = false)
private AclClassName aclClassName;
AclClass() {
}
public AclClassName getAclClassName() {
return aclClassName;
}
void setAclClassName(AclClassName aclClassName) {
this.aclClassName = aclClassName;
}
}
@Entity
@Table(name = "role_")
@SequenceGenerator(sequenceName = "role_seq", name = "role_seq", allocationSize = 1)
public final class Role extends AuditingModel {
public static final int ROLE_NAME_MAX_LENGTH = 300;
private static final long serialVersionUID = -1247915702100609524L;
@Id
@Column(name = "id")
@GeneratedValue(generator = "role_seq", strategy = GenerationType.SEQUENCE)
private Long id;
@Enumerated(EnumType.STRING)
@Column(name = "role_type", nullable = false)
private RoleType roleType;
@Column(name = "name", unique = true, length = ROLE_NAME_MAX_LENGTH, nullable = false)
private String name;
@ManyToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
@JoinTable(name = "role_permission", joinColumns = {@JoinColumn(name = "role_id", nullable = false, updatable = false)}, inverseJoinColumns = {@JoinColumn(name = "permission_id", nullable = false, updatable = false)})
private Set<Permission> permissions = new HashSet<>();
private Role() {
}
private void setId(Long id) {
this.id = id;
}
public Long getId() {
return id;
}
private void setRoleType(RoleType roleType) {
this.roleType = roleType;
}
public RoleType getRoleType() {
return roleType;
}
private void setName(String name) {
this.name = name;
}
public String getName() {
return name;
}
private void setPermissions(Set<Permission> permissions) {
this.permissions = permissions;
}
public Set<Permission> getPermissions() {
return permissions;
}
public void changeDetails(String name, RoleType type) {
setName(name);
setRoleType(type);
}
public void changePermissions(Set<Permission> permissions) {
getPermissions().clear();
getPermissions().addAll(permissions);
}
public static Builder builder() {
return new Builder();
}
public static final class Builder extends EntityBuilder<Role> {
private Builder() {
}
@Override
protected void initProduct() {
this.product = new Role();
}
public Builder name(String name) {
this.product.setName(name);
return this;
}
public Builder type(RoleType roleType) {
this.product.setRoleType(roleType);
return this;
}
}
}
通过引入实体,项目对对象实例的构造施加了一定的访问标准。这个标准的目的在于确保实体的实例是安全的,即不会因为我们所不期望的原因发生改变。
在 Role
类的实现里面,我们应该可以看到所有的成员变量和设置成员变量的方法(setter)都是私有的。只有获取成员变量的方法(getter)是公有的,另外构造方法(constructor)也是私有的。利用这种安排,使用这一实体的开发者是不会用与专门提供的工厂方法之外的方式创建该类的实例的。这个实体会具有一个 builder()
类方法,该方法会返回一个 Builder
内部类的对象。项目中的具体实体的工厂类可以从项目包中扩展 EntityBuilder
类来快速构建。
Abixen 平台的存储仓库建立在 Spring Data 上。注意,领域本身不会持有存储仓库的具体实现。领域里面应该避免出现各种像对数据库的访问这样的外部访问。开发者可以使用 Spring Data 创建纯粹的接口,而不用编写这一接口的实现。不过,在我们需要特定实现的时候,比如需要自定义的存储行为时,开发者则应该在领域层创建一个接口,但在基础结构中编写它的具体实现。这里便给出位于领域层的 PlatformJpaRepository
接口:
@NoRepositoryBean
public interface PlatformJpaRepository<T, ID extends Serializable> extends JpaRepository<T, ID> {
List<T> findAll(SearchForm searchForm);
Page<T> findAll(Pageable pageable, SearchForm searchForm);
List<T> findAll(SearchForm searchForm, User user, AclClassName aclClassName, PermissionName permissionName);
Page<T> findAll(Pageable pageable, SearchForm searchForm, User user, AclClassName aclClassName, PermissionName permissionName);
List<T> findAll(User user, AclClassName aclClassName, PermissionName permissionName);
Page<T> findAll(Pageable pageable, User user, AclClassName aclClassName, PermissionName permissionName);
}
它的实现类 PlatformJpaRepositoryImpl
则放置在基础结构层中。
领域中的服务的实现形式其实就是 Spring 框架的服务。我们能把领域层里的另一个服务还有存储仓库注入到一个服务里面。显然,领域的服务的意义就在于封装对存储仓库以及其他领域服务的调用。在这些服务中,我们可以编写仅针对于领域的逻辑。领域服务只能在领域类上运行。这里给出 RoleService
的一个实现:
@Slf4j
@Transactional
@PlatformDomainService
public class RoleService {
private final RoleRepository roleRepository;
private final AclSidService aclSidService;
@Autowired
public RoleService(RoleRepository roleRepository,
AclSidService aclSidService) {
this.roleRepository = roleRepository;
this.aclSidService = aclSidService;
}
public Role find(final Long id) {
log.debug("find() - id: {}", id);
return roleRepository.findOne(id);
}
public List<Role> findAll() {
log.debug("findAll()");
return roleRepository.findAll();
}
public Page<Role> findAll(final Pageable pageable, final RoleSearchForm roleSearchForm) {
log.debug("findAll() - pageable: {}, roleSearchForm: {}", pageable, roleSearchForm);
return roleRepository.findAll(pageable, roleSearchForm);
}
public Role create(final Role role) {
log.debug("create() - role: {}", role);
Role createdRole = roleRepository.save(role);
aclSidService.create(AclSidType.ROLE, createdRole.getId());
return createdRole;
}
public Role update(final Role role) {
log.debug("update() - role: {}", role);
return roleRepository.save(role);
}
public void delete(final Long id) {
log.debug("delete() - id: {}", id);
try {
roleRepository.delete(id);
} catch (Throwable e) {
e.printStackTrace();
if (e.getCause() instanceof ConstraintViolationException) {
log.warn("The role id: {} you want to remove is assigned to users.", id);
throw new PlatformRuntimeException("The role you want to remove is assigned to users.");
} else {
throw e;
}
}
}
}
如上所示,该服务依赖于 RoleRepository
和 AclSidService
。这一服务同时也负责 Role
这一领域类的处理。
基础结构层会包含我们所给出的微服务的所有技术细节。它包含所有可供其他层或仅由应用层使用的常用服务和一些全局的配置。例如,Abixen 平台将 JPA 的配置保留在基础结构层里的 CoreJpaConfiguration
类中,如下所示:
@Configuration
@Import(CoreDataSourceConfiguration.class)
@EnableTransactionManagement
@EnableJpaAuditing(auditorAwareRef = "platformAuditorAware")
@EnableJpaRepositories(basePackageClasses = CoreApplication.class,
repositoryFactoryBeanClass = CoreJpaRepositoryFactoryBean.class)
public class CoreJpaConfiguration extends AbstractJpaConfiguration {
@Autowired
public CoreJpaConfiguration(DataSource dataSource, AbstractPlatformJdbcConfigurationProperties platformJdbcConfiguration) {
super(dataSource, platformJdbcConfiguration, CoreApplication.class.getPackage().getName());
}
public AuditorAware platformAuditorAware() {
return new PlatformAuditorAware();
}
}
存储仓库接口的实现也会放在基础结构层,比如 PlatformJpaRepositoryImpl
:
public class PlatformJpaRepositoryImpl<T, ID extends Serializable>
extends SimpleJpaRepository<T, ID> implements PlatformJpaRepository<T, ID> {
private final EntityManager entityManager;
public PlatformJpaRepositoryImpl(JpaEntityInformation<T, ?> entityInformation, EntityManager entityManager) {
super(entityInformation, entityManager);
this.entityManager = entityManager;
}
public Page<T> findAll(Pageable pageable, SearchForm searchForm, User user, AclClassName aclClassName, PermissionName permissionName) {
Specification<T> securedSpecification = SecuredSpecifications.getSpecification(user, aclClassName, permissionName);
Specification<T> searchFormSpecification = SearchFormSpecifications.getSpecification(searchForm);
Specification<T> specification = AndSpecifications.getSpecification(searchFormSpecification, securedSpecification);
return (Page) (null == pageable ? new PageImpl(this.findAll()) : this.findAll(Specifications.where(specification), pageable));
}
public List<T> findAll(SearchForm searchForm, User user, AclClassName aclClassName, PermissionName permissionName) {
Specification<T> securedSpecification = SecuredSpecifications.getSpecification(user, aclClassName, permissionName);
Specification<T> searchFormSpecification = SearchFormSpecifications.getSpecification(searchForm);
Specification<T> specification = AndSpecifications.getSpecification(searchFormSpecification, securedSpecification);
return this.findAll(Specifications.where(specification));
}
public Page<T> findAll(Pageable pageable, User user, AclClassName aclClassName, PermissionName permissionName) {
Specification<T> securedSpecification = SecuredSpecifications.getSpecification(user, aclClassName, permissionName);
return (Page) (null == pageable ? new PageImpl(this.findAll()) : this.findAll(Specifications.where(securedSpecification), pageable));
}
public List<T> findAll(User user, AclClassName aclClassName, PermissionName permissionName) {
Specification<T> securedSpecification = SecuredSpecifications.getSpecification(user, aclClassName, permissionName);
return this.findAll(Specifications.where(securedSpecification));
}
public Page<T> findAll(Pageable pageable, SearchForm searchForm) {
Specification<T> searchFormSpecification = SearchFormSpecifications.getSpecification(searchForm);
return (Page) (null == pageable ? new PageImpl(this.findAll()) : this.findAll(Specifications.where(searchFormSpecification), pageable));
}
public List<T> findAll(SearchForm searchForm) {
Specification<T> searchFormSpecification = SearchFormSpecifications.getSpecification(searchForm);
return this.findAll(Specifications.where(searchFormSpecification));
}
}
因此,领域就可以实现与数据库访问的解耦。毕竟领域只持有一个接口,并不会知道接口的具体实现是什么。
再次强调,Abixen 项目尚未完全实现领域驱动设计(DDD)。本文介绍的内容只是一个关于 DDD 主题的部分有趣的实现方法的总结,而这个方法使用了有很多基于开源项目的示例支持的 Java 8 和 Spring 框架等技术。将来可能会出现更多的 DDD 构件的实现。在此我由衷希望上面的例子能让大家根据 DDD 的概念来设计自己的应用。
若想分享你的建设性意见,我将非常高兴参加讨论。
腾讯分布式微服务TSF围绕应用和微服务的PaaS平台,提供服务全生命周期管理能力和数据化运营支持,提供多维度应用、服务、机器的监控数据,助力服务性能优化;拥抱 Spring Cloud 开源社区技术更新和K8s容器化部署。