# nest-plus
**Repository Path**: zzpcoder/nest-plus
## Basic Information
- **Project Name**: nest-plus
- **Description**: No description available
- **Primary Language**: Java
- **License**: Not specified
- **Default Branch**: master
- **Homepage**: None
- **GVP Project**: No
## Statistics
- **Stars**: 0
- **Forks**: 0
- **Created**: 2024-05-27
- **Last Updated**: 2024-05-27
## Categories & Tags
**Categories**: Uncategorized
**Tags**: None
## README
# Nest最佳实践:nest-plus
nest-plus是一套基于spring容器的nest的扩展。nest-plus简化了nest的使用方法,尤其是spring boot环境下。
nest-plus不仅仅集成了spring,还集成了activemq、rocketmq、rabbitmq、redis、automapper等组件,让nest的使用如丝更顺滑。
## 快速开始
> 参考演示代码 [nest-plus-demo](https://github.com/jovezhao/nest-plus/tree/master/nest-plus-demo)
>
### 第一步:添加引用
**使用Maven方式添加引用**
```xml
com.zhaofujun.nest
nest-plus-spring-boot-starter
2.2.10
```
*使用Gradle方式添加引用*
```groovy
implementation 'com.zhaofujun.nest:nest-plus-spring-boot-starter:2.2.10'
```
在`nest-plus-spring-boot-starter`模块使用`SpringBeanContainerProvider`集成了Spring的IOC来完成Bean容器的支持
使用`NestAspect`集成了Spring的AOP实现了应用服务的切面处理,自动完成对工作单元的提交。
通过`NestAutoConfiguration`完成nest的自动配置,包括`NestApplication`和`EventBus`等。
### 第二步:创建spring boot应用程序
```java
package com.zhaofujun.nest.demo;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}
```
### 第三步:实现领域模型
```java
package com.zhaofujun.nest.demo.domain;
import com.zhaofujun.nest.CustomException;
import com.zhaofujun.nest.context.model.StringIdentifier;
import com.zhaofujun.nest.core.BaseEntity;
public class User extends BaseEntity {
private String name;
private int age;
public String getName() {
return name;
}
public int getAge() {
return age;
}
public void init(String name, int age) {
this.name = name;
this.age = age;
}
public void changeAge(int age) {
this.age = age;
}
@Override
public void verify() {
if (age < 10) {
throw new EntityVerifyException("年龄太小了");
}
}
}
```
### 第四步:添加应用服务
```java
package com.zhaofujun.nest.demo.application;
import com.zhaofujun.nest.context.model.StringIdentifier;
import com.zhaofujun.nest.core.EntityFactory;
import com.zhaofujun.nest.core.EventBus;
import com.zhaofujun.nest.demo.contract.TestEventData;
import com.zhaofujun.nest.demo.contract.UserService;
import com.zhaofujun.nest.demo.domain.User;
import com.zhaofujun.nest.spring.AppService;
import org.springframework.beans.factory.annotation.Autowired;
@AppService
public class UserAppService implements UserService {
public void create() {
User user = EntityFactory.createOrLoad(User.class, StringIdentifier.valueOf("111"));
user.init("老赵", 5);
user.changeAge(50);
eventBus.publish(eventData);
User use = EntityFactory.createOrLoad(User.class, StringIdentifier.valueOf("111"));
use.changeAge(20);
}
public void changeAge() {
User use = EntityFactory.load(User.class, StringIdentifier.valueOf("111"));
use.changeAge(20);
}
}
```
### 第五步:添加Rest适配器
```java
package com.zhaofujun.nest.demo.adapter.rest;
import com.zhaofujun.nest.demo.contract.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class DemoController {
@Autowired
private UserService userService;
@GetMapping("/create")
public String create() {
userService.create();
return "publish";
}
@GetMapping("/change")
public String change() {
userService.changeAge();
return "change";
}
}
```
## 配置管理
### 不自动注册EventHandler
当开发调试时,我们不需要自动注册所有的EventHandler,可以使用配置参数`nest.events.auto=false`来关闭自动注册。
### 配置事件与通道的关系
`nest.events.事件代号=通道代号`
### 配置缓存组信息
`nest.caches.缓存组代号.provider=缓存提供者代号`
`nest.caches.缓存组代号.idleSeconds=过期时间`
`nest.caches.缓存组代号.name=名称`
### 配置消息
`nest.message.store=消息存储器代号`
`nest.message.converter=消息转换器代号`
`nest.message.resendStore=异常消息暂存器代号`
`nest.message.delayStore=延迟消息暂存器代号`
### 配置锁提供者
`nest.lock.provider=锁提供者代号`
`nest.lock.retry=重试获取锁的次数`
`nest.lock.timeout=锁超时时间`
## 使用原则
### 异常处理
Nest为用户定义了两个级别的异常,分别是系统异常和业务异常。
**系统异常**
表示程序未考虑到的,需要程序员优化处理的异常统称为系统异常。系统异常自动使用Error级别的日志来记录当前异常状态
系统异常一般不对用户开放,所有系统异常都应该统一一个信息告知用户。
**业务异常**
在系统设计过程中事先计划好的,按业务术语返回给用户知悉的异常。业务异常使用Info级别的日志来记录。
在定义业务异常时需要继承`com.zhaofujun.nest.CustomException`实现。 如果您公司规划中已有定义了业务异常的父类,我们也可以使用`com.zhaofujun.nest.CustomExceptionable`接口来实现集成
### DDD-分层架构
在DDD中,系统一般会为用户界面层、应用服务层、领域层、基础设施层。
**微服务下,基于nest的应用架构图**

**用户界面层**
负责向用户显示信息和解释用户指令。它不仅仅是与用户的界面的交互,还包括使用指定的方式将系统的能力对外开放或集成。所以这里指的用户可以是另一个计算机系统,不一定是使用用户界面的人
随着现在前后端分离、微服务架构的发展,用户界面层也发生了很大的改变。甚至可能跨越WEB/APP前端、微服务网关和RestController等。
**应用服务层**
定义系统具备的所有能力,以用例为基础保证事务一致性,调用领域层的业务实现来组装业务功能。
应用服务层主要完成
- 服务契约、行为契约与数据契约定义和实现
- 事务一致性保障
- 通过领域层的业务实现来组装业务功能
- 使用Assembler来完成数据契约与领域实体的转换
**领域层**
系统的核心业务层,由领域模型与领域服务组成,是领域战术设计的核心体现。
**基础设施层**
提供基础设施的集成功能,包括但不限于文件、缓存、数据库等。
推荐将领域模型的仓储实现放在基础设施中,以保证领域层只关注业务逻辑而不需要关注数据,包括对数据库操作的数据库访问对象DAO、访问方法等。
### DDD-六边形架构

六边形架构是也是一种分层架构的体现,他的分层视图不在于上下,而在于内外。
六边形架构将系统当成由对内或对外的端口组成,每一个端口又对应着一种适配器。而系统的独心独立,由应用服务及其内部的领域模型组成。
对内的适配器接入外部请求,调用对应的应用服务完成业务请求。
对外的适配器集成三方系统,如数据库、缓存、其它服务等,通过依赖反转的原则供应用服务层调用。
使用六边形架构思想设计的产品,核心功能独立,与外部无直接依赖,方便基于mock的单元测试。
## 缓存通道扩展与集成
Nest支持用户自定义集成第三方缓存系统,只需要实现`com.zhaofujun.nest.cache.provider.CacheProvider`接口即可。
在nest-plus中,使用`nest-plus-spring-redis`实现了对redis的集成,该集成方案基于`spring-boot-starter-data-redis`。
如果您的项目中已经使用了`spring-boot-starter-data-redis`来集成redis,添加`nest-plus-spring-redis`模块的依赖即可完成redis的集成。
如果您的项目没有使用过`spring-boot-starter-data-redis`,请参考[官方配置参数](https://spring.io/projects/spring-data-redis#overview)完成redis相关配置
对应的缓存提供者代号为`com.zhaofujun.nest.redis.provider.RedisCacheProvider.CODE`。
**演示一:将领域实体使用redis来缓存**
```java
package com.zhaofujun.nest.demo;
import com.zhaofujun.nest.NestApplication;
import com.zhaofujun.nest.event.ApplicationEvent;
import com.zhaofujun.nest.event.ApplicationListener;
import com.zhaofujun.nest.event.ServiceContextListener;
import com.zhaofujun.nest.event.ServiceEvent;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.lang.reflect.Method;
@Configuration
public class DemoConfiguration {
@Bean
public CacheConfiguration entityCacheConfiguration() {
CacheConfiguration cacheConfiguration = new CacheConfiguration();
cacheConfiguration.setCacheCode(EntityCacheUtils.getCacheCode());
cacheConfiguration.setName("实体缓存策略");
cacheConfiguration.setProviderCode(RedisCacheProvider.CODE);
return cacheConfiguration;
}
}
```
**演示二:将事件消息使用redis来存储**
```java
package com.zhaofujun.nest.demo;
import com.zhaofujun.nest.NestApplication;
import com.zhaofujun.nest.event.ApplicationEvent;
import com.zhaofujun.nest.event.ApplicationListener;
import com.zhaofujun.nest.event.ServiceContextListener;
import com.zhaofujun.nest.event.ServiceEvent;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.lang.reflect.Method;
@Configuration
public class DemoConfiguration {
@Bean
public CacheConfiguration messageStoreConfiguration() {
CacheConfiguration cacheConfiguration = new CacheConfiguration();
cacheConfiguration.setCacheCode(DefaultMessageStore.CACHE_CODE);
cacheConfiguration.setName("事件消息存储策略");
cacheConfiguration.setProviderCode(RedisCacheProvider.CODE);
return cacheConfiguration;
}
}
```
## 事件通道扩展与集成
### 事件的发起与处理
**演示:创建事件数据对象**
```java
package com.zhaofujun.nest.demo.contract;
import com.zhaofujun.nest.core.EventData;
public class TestEventData extends EventData {
public static final String Code="testEvent";
private String data;
public String getData() {
return data;
}
public void setData(String data) {
this.data = data;
}
@Override
public String getEventCode() {
return Code;
}
@Override
public String toString() {
return "TestEventData{" +
"data='" + data + '\'' +
'}';
}
}
```
**发起事件**
```java
package com.zhaofujun.nest.demo.application;
import com.zhaofujun.nest.context.model.StringIdentifier;
import com.zhaofujun.nest.core.EntityFactory;
import com.zhaofujun.nest.core.EventBus;
import com.zhaofujun.nest.demo.contract.TestEventData;
import com.zhaofujun.nest.demo.contract.UserService;
import com.zhaofujun.nest.demo.domain.User;
import com.zhaofujun.nest.spring.AppService;
import org.springframework.beans.factory.annotation.Autowired;
@AppService
public class UserAppService implements UserService {
@Autowired
private EventBus eventBus;
public void create() {
User user = EntityFactory.createOrLoad(User.class, StringIdentifier.valueOf("111"));
user.init("老赵", 5);
user.changeAge(50);
TestEventData eventData = new TestEventData();
eventData.setData("test event data");
eventBus.publish(eventData);
User use = EntityFactory.createOrLoad(User.class, StringIdentifier.valueOf("111"));
use.changeAge(20);
}
}
```
**处理事件**
```java
package com.zhaofujun.nest.demo.adapter.event;
import com.zhaofujun.nest.context.event.EventArgs;
import com.zhaofujun.nest.core.EventHandler;
import com.zhaofujun.nest.demo.contract.TestEventData;
import org.springframework.stereotype.Component;
@Component
public class DemoEventHandle implements EventHandler {
@Override
public String getEventCode() {
return TestEventData.Code;
}
@Override
public Class getEventDataClass() {
return TestEventData.class;
}
@Override
public void handle(TestEventData testEventData, EventArgs eventArgs) {
System.out.println(testEventData.toString());
}
}
```
Nest的事件处理通道默认使用本地事件通道,本地事件通道基于内存同步处理。当前也支持用户自定义第三方消息中间件作为事件通道,集成第三方事件通道需要实现接口`com.zhaofujun.nest.context.event.channel.distribute.DistributeMessageProducer`
在nest-plus中,我们扩展了ActiveMQ、RabbitMQ和RocketMQ消息中间件的集成。
### ActiveMQ的集成与使用
在nest-plus中,添加`nest-plus-spring-activemq`模块即可完成ActiveMQ的集成,该模块使用`spring-boot-starter-activemq`为基础,相关配置参考[官方文档](https://docs.spring.io/spring-boot/docs/current/reference/html/spring-boot-features.html#boot-features-jms)
**演示:配置使用ActiveMQ通道发送事件**
```java
package com.zhaofujun.nest.demo;
import com.zhaofujun.nest.NestApplication;
import com.zhaofujun.nest.event.ApplicationEvent;
import com.zhaofujun.nest.event.ApplicationListener;
import com.zhaofujun.nest.event.ServiceContextListener;
import com.zhaofujun.nest.event.ServiceEvent;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.lang.reflect.Method;
@Configuration
public class DemoConfiguration {
@Bean
public EventConfiguration testEventConfiguration() {
EventConfiguration eventConfiguration = new EventConfiguration();
eventConfiguration.setEventCode(DemoEventData.Code);
eventConfiguration.setMessageChannelCode(ActiveMQMessageChannel.CHANNEL_CODE);
return eventConfiguration;
}
}
```
### RabbitMQ的集成与使用
在nest-plus中,添加`nest-plus-spring-rabbitmq`模块即可完成RabbitMQ的集成,该模块使用`spring-boot-starter-amqp`为基础,相关配置参考[官方文档](https://docs.spring.io/spring-boot/docs/current/reference/html/spring-boot-features.html#boot-features-amqp)
**演示:配置使用RabbitMQ通道发送事件**
```java
package com.zhaofujun.nest.demo;
import com.zhaofujun.nest.NestApplication;
import com.zhaofujun.nest.event.ApplicationEvent;
import com.zhaofujun.nest.event.ApplicationListener;
import com.zhaofujun.nest.event.ServiceContextListener;
import com.zhaofujun.nest.event.ServiceEvent;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.lang.reflect.Method;
@Configuration
public class DemoConfiguration {
@Bean
public EventConfiguration testEventConfiguration() {
EventConfiguration eventConfiguration = new EventConfiguration();
eventConfiguration.setEventCode(DemoEventData.Code);
eventConfiguration.setMessageChannelCode(RabbitMQMessageChannel.CHANNEL_CODE);
return eventConfiguration;
}
}
```
### RocketMQ的集成与使用
在nest-plus中,添加`nest-plus-spring-rocketmq`模块即可完成RabbitMQ的集成,该模块使用`org.apache.rocketmq:rocketmq-spring-boot-starter`为基础,相关配置参考[官方文档](https://github.com/apache/rocketmq-spring)
**演示:配置使用RocketMQ通道发送事件**
```java
package com.zhaofujun.nest.demo;
import com.zhaofujun.nest.NestApplication;
import com.zhaofujun.nest.event.ApplicationEvent;
import com.zhaofujun.nest.event.ApplicationListener;
import com.zhaofujun.nest.event.ServiceContextListener;
import com.zhaofujun.nest.event.ServiceEvent;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.lang.reflect.Method;
@Configuration
public class DemoConfiguration {
@Bean
public EventConfiguration testEventConfiguration() {
EventConfiguration eventConfiguration = new EventConfiguration();
eventConfiguration.setEventCode(DemoEventData.Code);
eventConfiguration.setMessageChannelCode(RocketMQMessageChannel.CHANNEL_CODE);
return eventConfiguration;
}
}
```
## 集成AutoMapper
AutoMapper提供Bean的映射能力,可以帮助我们将领域对象转换为DTO对象,以及领域对象与持久化对象的互相转换。
AutoMapper允许目标对象对没有setter方法的字段进行映射。
> AutoMapper的使用请参考[官方文档](https://github.com/jovezhao/automapper)
`nest-plus-spring-automapper`模块提供了AutoMapper的spring集成方式,并且内置了`LongIdentifierConverter`、`StringIdentifierConverter`,`UUIdentifierConverter`转换器,并且将`AutoMapper`类放入容器中,我们可以直接注入`IMapper`来获取AutoMapper的能力。
**使用AutoMapper映射领域对象与持久化对象**
```java
package com.zhaofujun.nest.demo.infrastructure.repository;
import com.zhaofujun.automapper.AutoMapper;
import com.zhaofujun.nest.context.model.EntityExistedException;
import com.zhaofujun.nest.core.EntityLoader;
import com.zhaofujun.nest.core.Identifier;
import com.zhaofujun.nest.core.Repository;
import com.zhaofujun.nest.demo.domain.User;
import com.zhaofujun.nest.demo.infrastructure.persistence.UserDmo;
import com.zhaofujun.nest.demo.infrastructure.persistence.service.IUserDmoService;
import com.zhaofujun.nest.demo.infrastructure.persistence.mapper.UserDmoMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.DuplicateKeyException;
import org.springframework.stereotype.Component;
import java.util.List;
import java.util.stream.Collectors;
@Component
public class UserRepository implements Repository {
@Autowired
private UserDmoMapper userDmoMapper;
@Autowired
private AutoMapper autoMapper;
@Autowired
private IUserDmoService userDmoService;
@Override
public Class getEntityClass() {
return User.class;
}
@Override
public User getEntityById(Identifier identifier, EntityLoader entityLoader) {
UserDmo userDmo = userDmoMapper.selectById(identifier.toValue());
if (userDmo == null) return null;
User user = entityLoader.create(identifier);
autoMapper.map(userDmo, user);
return user;
}
@Override
public void insert(User use) {
UserDmo userDmo = autoMapper.map(use, UserDmo.class);
try {
userDmoMapper.insert(userDmo);
} catch (DuplicateKeyException ex) {
throw new EntityExistedException("用户已经存在") {
};
}
}
@Override
public void update(User use) {
UserDmo userDmo = autoMapper.map(use, UserDmo.class);
userDmoMapper.updateById(userDmo);
}
@Override
public void delete(User user) {
userDmoMapper.deleteById(user.getId().getId());
}
@Override
public void batchInsert(List users) {
List userDmoList = users.stream().map(p -> autoMapper.map(p, UserDmo.class)).collect(Collectors.toList());
try {
userDmoService.saveBatch(userDmoList);
} catch (DuplicateKeyException ex) {
throw new EntityExistedException("用户已经存在") {
};
}
}
}
```
**自定义Mapper配置ClassMapping**
```java
package com.zhaofujun.nest.demo;
import com.zhaofujun.automapper.builder.ClassMappingBuilder;
import com.zhaofujun.automapper.mapping.ClassMapping;
import com.zhaofujun.nest.NestApplication;
import com.zhaofujun.nest.demo.domain.User;
import com.zhaofujun.nest.demo.infrastructure.persistence.UserDmo;
import com.zhaofujun.nest.event.ApplicationEvent;
import com.zhaofujun.nest.event.ApplicationListener;
import com.zhaofujun.nest.event.ServiceContextListener;
import com.zhaofujun.nest.event.ServiceEvent;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.lang.reflect.Method;
@Configuration
public class DemoConfiguration {
@Bean
public ClassMapping UserDmo2Do() {
return new ClassMapping(UserDmo.class, User.class)
.field("id", "id");
}
}
```
**自定义转换器**
```java
package com.zhaofujun.nest.demo;
import com.zhaofujun.automapper.builder.ClassMappingBuilder;
import com.zhaofujun.automapper.mapping.ClassMapping;
import com.zhaofujun.nest.NestApplication;
import com.zhaofujun.nest.demo.domain.User;
import com.zhaofujun.nest.demo.infrastructure.persistence.UserDmo;
import com.zhaofujun.nest.event.ApplicationEvent;
import com.zhaofujun.nest.event.ApplicationListener;
import com.zhaofujun.nest.event.ServiceContextListener;
import com.zhaofujun.nest.event.ServiceEvent;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.lang.reflect.Method;
@Configuration
public class DemoConfiguration {
@Bean
public Converter TestConverter(){
return new TestConverter();
}
}
```
# 常见问题