Mejores prácticas de arquitectura en capas DDD

Cuando todavía está en una sola aplicación, es una arquitectura en capas. Usamos más la arquitectura de tres niveles. Ahora es la era de los microservicios. Hay varios modelos de arquitectura de microservicios de uso común, como: arquitectura limpia, CQRS (separación de consultas de comandos) y arquitectura hexagonal. Cada modelo de arquitectura tiene su propio escenario de aplicación, pero su núcleo es el principio de " alta cohesión y bajo acoplamiento ". Utilizando el concepto de diseño impulsado por dominios (DDD) para hacer frente al impacto de los cambios comerciales acelerados diarios en la arquitectura, los límites de la arquitectura se están volviendo más claros y cada uno realiza sus propias funciones, que también está en línea con la filosofía de diseño de arquitectura de microservicio. La arquitectura en capas basada en el concepto de Diseño controlado por dominio (DDD) se ha convertido en el método de mejores prácticas para la práctica de la arquitectura de microservicios.

1. ¿Qué es la arquitectura en capas DDD?

1. Arquitectura tradicional de tres niveles

Para comprender la arquitectura en capas de DDD, primero comprenda la arquitectura tradicional de tres niveles.

Proceso de arquitectura tradicional de tres niveles:

  • El primer paso es considerar el diseño de la base de datos, cómo construir la tabla de datos, cómo diseñar la relación entre las tablas.
  • El segundo paso es construir una capa de acceso a datos, como elegir un marco ORM o empalmar operaciones SQL
  • El tercer paso es la realización de la lógica empresarial. Dado que diseñamos la base de datos primero, todo nuestro pensamiento girará en torno a la base de datos, pensando en cómo escribir datos para escribir correctamente los datos en la base de datos. En este momento, la práctica estándar de CRUD. No hay mucha consideración sobre la orientación a objetos y el desacoplamiento. Naturalmente, este código es cada vez más difícil para el mantenimiento diario
  • El cuarto paso representa la salida de la capa principalmente para los usuarios.

2. Arquitectura en capas DDD

Con el fin de resolver el problema de alto acoplamiento y hacer frente fácilmente a futuros cambios del sistema, propusimos el concepto de utilizar un diseño dirigido por dominios para diseñar la arquitectura.

Este párrafo resume en parte los pensamientos después de leer "07 | Arquitectura en capas DDD: Reducir eficazmente la dependencia entre capas" del "Curso de práctica DDD" de Ou Chuangxin

1) Capa de dominio

En primer lugar, dejemos a un lado el problema de la base de datos, comencemos con la lógica empresarial y dejemos de considerar la implementación de la base de datos al diseñar. Divida la capa de lógica empresarial anterior (BLL) en una capa de dominio y una capa de aplicación.

La capa de dominio se centra en la realización de la lógica empresarial de los objetos empresariales, lo que refleja los cambios lógicos de los negocios del mundo real. Se utiliza para expresar conceptos comerciales, estado comercial y reglas comerciales. Para el análisis comercial, puede consultar: "Analizar negocios utilizando un diseño basado en dominios".

2) Capa de aplicación

La capa de aplicación es la capa superior de la capa de dominio, que depende de la capa de dominio, es la coordinación y orquestación de agregaciones y, en principio, no incluye ninguna lógica empresarial. Proporciona soporte para la interfaz frontal con un cierre de grano más grueso. Además de proporcionar llamadas de nivel superior, también puede incluir suscripción a eventos y mensajes.

3) Capa de interfaz de usuario

La capa de interfaz de usuario está orientada a la interfaz de entrada de datos para el acceso del usuario y puede proporcionar diferentes implementaciones de interfaz de usuario de acuerdo con diferentes escenarios. Los servicios orientados a la web se pueden proporcionar en modo http restful, que puede agregar funciones como autenticación de seguridad, verificación de permisos y registro; los servicios orientados a microservicios se pueden proporcionar en modo RPC, y funciones como la limitación y fusión de corriente pueden ser adicional.

4) Capa de infraestructura

La capa de infraestructura es la interfaz de salida de datos, que encapsula los detalles técnicos de la invocación de datos. Puede proporcionar servicios para cualquier otra capa, pero para resolver el problema de acoplamiento, se adopta el principio de inversión de dependencia. Las otras capas solo dependen de la interfaz de la infraestructura y están separadas de la implementación específica.

Implementación de código en dos capas DDD

1. Modelo estructural

2. Estructura de directorio

.
├── pom.xml
└── src
    ├── main
    │   ├── java
    │   │   └── fun
    │   │       └── barryhome
    │   │           └── ddd
    │   │               ├── WalletApplication.java
    │   │               ├── application
    │   │               │   ├── TradeEventProcessor.java
    │   │               │   ├── TradeMQReceiver.java
    │   │               │   └── TradeManager.java
    │   │               ├── constant
    │   │               │   └── MessageConstant.java
    │   │               ├── controller
    │   │               │   ├── TradeController.java
    │   │               │   ├── WalletController.java
    │   │               │   └── dto
    │   │               │       └── TradeDTO.java
    │   │               ├── domain
    │   │               │   ├── TradeService.java
    │   │               │   ├── TradeServiceImpl.java
    │   │               │   ├── enums
    │   │               │   │   ├── InOutFlag.java
    │   │               │   │   ├── TradeStatus.java
    │   │               │   │   ├── TradeType.java
    │   │               │   │   └── WalletStatus.java
    │   │               │   ├── event
    │   │               │   │   └── TradeEvent.java
    │   │               │   ├── model
    │   │               │   │   ├── BaseEntity.java
    │   │               │   │   ├── TradeRecord.java
    │   │               │   │   └── Wallet.java
    │   │               │   └── repository
    │   │               │       ├── TradeRepository.java
    │   │               │       └── WalletRepository.java
    │   │               └── infrastructure
    │   │                   ├── TradeRepositoryImpl.java
    │   │                   ├── WalletRepositoryImpl.java
    │   │                   ├── cache
    │   │                   │   └── Redis.java
    │   │                   ├── client
    │   │                   │   ├── AuthFeignClient.java
    │   │                   │   └── LocalAuthClient.java
    │   │                   ├── jpa
    │   │                   │   ├── JpaTradeRepository.java
    │   │                   │   └── JpaWalletRepository.java
    │   │                   └── mq
    │   │                       └── RabbitMQSender.java
    │   └── resources
    │       ├── application.properties
    │       └── rabbitmq-spring.xml
    └── test
        └── java



Esta estructura es una estructura simple de un solo microservicio, con todas las capas en el mismo módulo.

En el proceso de desarrollo de proyectos a gran escala, para lograr el control de autoridad del módulo central o una mejor flexibilidad, la estructura se puede ajustar adecuadamente, consulte la estructura del sistema "Sistema de billetera digital"

3. Realización de la capa de dominio (dominio)

Después del análisis comercial ("Análisis de negocios mediante el diseño basado en dominios"), comience a escribir código. El primero es escribir la capa de dominio, crear objetos de dominio e interfaces de servicio de dominio

1) Objeto de dominio

Los objetos de dominio aquí incluyen objetos de entidad y objetos de valor.

Objeto de entidad : un objeto que tiene un identificador único, puede existir solo y puede cambiarse

Objeto de valor : un objeto que no puede existir solo o existe solo en el nivel lógico, sin sentido e inmutable

Agregación : una colección de múltiples objetos, externamente como un todo

Raíz de agregación : el objeto de entidad en la agregación que puede representar toda la operación comercial, a través del cual se proporcionan operaciones de acceso externo, mantiene la consistencia de los datos dentro de la agregación y es el administrador de los objetos en la agregación.

Ejemplo de código:

// 交易
public class TradeRecord extends BaseEntity {
    /**
     * 交易号
     */
    @Column(unique = true)
    private String tradeNumber;
    /**
     * 交易金额
     */
    private BigDecimal tradeAmount;
    /**
     * 交易类型
     */
    @Enumerated(EnumType.STRING)
    private TradeType tradeType;
    /**
     * 交易余额
     */
    private BigDecimal balance;
    /**
     * 钱包
     */
    @ManyToOne
    private Wallet wallet;

    /**
     * 交易状态
     */
    @Enumerated(EnumType.STRING)
    private TradeStatus tradeStatus;

  	@DomainEvents
    public List<Object> domainEvents() {
        return Collections.singletonList(new TradeEvent(this));
    }
}

// 钱包
public class Wallet extends BaseEntity {

    /**
     * 钱包ID
     */
    @Id
    private String walletId;
    /**
     * 密码
     */
    private String password;
    /**
     * 状态
     */
    @Enumerated(EnumType.STRING)
    private WalletStatus walletStatus = WalletStatus.AVAILABLE;
    /**
     * 用户Id
     */
    private Integer userId;
    /**
     * 余额
     */
    private BigDecimal balance = BigDecimal.ZERO;

}



Desde el diseño del sistema del ejemplo de transacción de la billetera , cualquier operación de la billetera, como recarga, mensaje, etc., impulsa el cambio del saldo de la billetera a través del objeto de transacción.

  • El objeto de transacción y el objeto de billetera son objetos de entidad y forman una relación de agregación. El objeto de transacción es la raíz de agregación del modelo comercial de transacción de billetera, que representa la agregación para proporcionar servicios de llamadas externas
  • Después de analizar la relación de uno a muchos entre el objeto de transacción y el objeto de billetera (@ManyToOne), JPA se utiliza como arquitectura ORM . Para más prácticas de JPA, consulte >>

El modelado de dominio aquí utiliza un modelo de anemia, que tiene una estructura simple, responsabilidades únicas, buen aislamiento, pero carece de ideas de diseño orientadas a objetos. Para el modelado de dominio, consulte "Modelo de anemia y modelo de congestión para el modelado de dominio"

  • domainEvents () es una implementación de la publicación de eventos de dominio. La función es que cualquier operación de datos del objeto de transacción activará la publicación del evento y luego cooperará con la suscripción del evento para implementar el modelo de diseño basado en eventos . Por supuesto, otros también son posibles las implementaciones

2) Servicios de dominio

/**
 * Created on 2020/9/7 11:40 上午
 *
 * @author barry
 * Description: 交易服务
 */
public interface TradeService {

    /**
     * 充值
     *
     * @param tradeRecord
     * @return
     */
    TradeRecord recharge(TradeRecord tradeRecord);

    /**
     * 消费
     *
     * @param tradeRecord
     * @return
     */
    TradeRecord consume(TradeRecord tradeRecord);
}



Primero, defina la interfaz del servicio. La definición de la interfaz debe seguir la operación del negocio real . No utilice la lógica del programa o la lógica de la base de datos para diseñar y definir adiciones, eliminaciones y cambios.

  • La principal dirección de pensamiento son los servicios que el objeto de transacción puede proporcionar externamente. La definición de este servicio es generalizada y altamente cohesiva . No defina algunos métodos específicos de nivel de implementación de código.
  • Los parámetros de entrada y salida de la interfaz deben considerarse en forma de objetos tanto como sea posible, totalmente compatibles con varios cambios de escena.
  • El complejo método de consulta requerido por el front-end no se define aquí. Generalmente, la consulta no es un servicio de dominio y no hay cambios en los datos, y se puede procesar por separado.
  • La realización del servicio de dominio se centra principalmente en la realización lógica, y no debe incluir código técnico básico, como realización de caché, realización de base de datos, llamada remota, etc.

3) Interfaz de infraestructura

public interface TradeRepository {
    /**
     * 保存
     * @param tradeRecord
     * @return
     */
    TradeRecord save(TradeRecord tradeRecord);

    /**
     * 查询订单
     * @param tradeNumber
     * @return
     */
    TradeRecord findByTradeNumber(String tradeNumber);

    /**
     * 发送MQ事件消息
     * @param tradeEvent
     */
    void sendMQEvent(TradeEvent tradeEvent);

    /**
     * 获取所有
     * @return
     */
    List<TradeRecord> findAll();
}



  • El objetivo principal de colocar la interfaz de infraestructura en la capa de dominio es reducir la dependencia de la capa de dominio en la capa de infraestructura.
  • El diseño de la interfaz no es para exponer los detalles técnicos de la implementación, por ejemplo, el SQL ensamblado no se puede utilizar como parámetro.

4. Implementación de la capa de aplicación (aplicación)

// 交易服务
@Component
public class TradeManager {

    private final TradeService tradeService;
    public TradeManager(TradeService tradeService) {
        this.tradeService = tradeService;
    }


    // 充值
    @Transactional(rollbackFor = Exception.class)
    public TradeRecord recharge(TradeRecord tradeRecord) {
        return tradeService.recharge(tradeRecord);
    }


     // 消费
    @Transactional(rollbackFor = Exception.class)
    public TradeRecord consume(TradeRecord tradeRecord) {
        return tradeService.consume(tradeRecord);
    }
}

// 交易事件订阅
@Component
public class TradeEventProcessor {

    @Autowired
    private TradeRepository tradeRepository;

    @TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT, condition = "# tradeEvent.tradeStatus.name() == 'SUCCEED'")
    public void TradeSucceed(TradeEvent tradeEvent) {
        tradeRepository.sendMQEvent(tradeEvent);
    }
}

// 交易消息订阅
@Component
public class TradeMQReceiver {

    @RabbitListener(queues = "ddd-trade-succeed")
    public void receiveTradeMessage(TradeEvent tradeEvent){
        System.err.println("========MQ Receiver============");
        System.err.println(tradeEvent);
    }
}



Servicio de aplicación :

  • La capa de aplicación es una capa muy fina, que se utiliza principalmente para llamar y combinar servicios de dominio, y no debe contener ninguna lógica empresarial.
  • Puede incluir una pequeña cantidad de juicio de parámetros de proceso
  • Dado que puede ser una llamada de operación combinada de múltiples servicios de dominio, si hay un requisito de atomicidad, puede agregar control de transacciones ** @ Transaccional **

Suscripción al evento :

  • La suscripción a eventos es una forma de realizar la cooperación y el desacoplamiento de operaciones en múltiples campos en un proceso, y también es el punto de acceso para todas las operaciones posteriores en el proceso.
  • Es diferente de la operación combinada de los servicios de la aplicación. La combinación se puede aumentar o disminuir según las necesidades de la escena, pero la operación después de la suscripción del evento está relativamente solidificada, principalmente para cumplir con los requisitos de coherencia lógica.

La configuración TransactionPhase.AFTER_COMMIT se llama después de que se completa la transacción de la operación anterior, lo que reduce el impacto de las operaciones posteriores en la operación anterior.

  • Las suscripciones a eventos pueden tener varios cuerpos de mensaje. Para facilitar la administración, es mejor manejarlos en una clase.
  • La publicación de mensajes MQ generalmente se coloca en la suscripción de eventos

Suscripción a noticias :

  • La suscripción de mensajes es una implementación en un solo paso de colaboración y desacoplamiento entre múltiples microservicios.
  • El cuerpo del mensaje se transmite en un paquete de objetos uniforme tanto como sea posible para reducir la dificultad de procesamiento causada por la heterogeneidad del objeto.

5. Capa de infraestructura (infraestructura)

@Repository
public class TradeRepositoryImpl implements TradeRepository {

    private final JpaTradeRepository jpaTradeRepository;
    private final RabbitMQSender rabbitMQSender;
    private final Redis redis;

    public TradeRepositoryImpl(JpaTradeRepository jpaTradeRepository, RabbitMQSender rabbitMQSender, Redis redis) {
        this.jpaTradeRepository = jpaTradeRepository;
        this.rabbitMQSender = rabbitMQSender;
        this.redis = redis;
    }

    @Override
    public TradeRecord save(TradeRecord tradeRecord) {
        return jpaTradeRepository.save(tradeRecord);
    }

    /**
     * 查询订单
     */
    @Override
    public TradeRecord findByTradeNumber(String tradeNumber) {
        TradeRecord tradeRecord = redis.getTrade(tradeNumber);
        if (tradeRecord == null){
            tradeRecord = jpaTradeRepository.findFirstByTradeNumber(tradeNumber);
            // 缓存
            redis.cacheTrade(tradeRecord);
        }

        return tradeRecord;
    }

    /**
     * 发送事件消息
     * @param tradeEvent
     */
    @Override
    public void sendMQEvent(TradeEvent tradeEvent) {
        // 发送消息
        rabbitMQSender.sendMQTradeEvent(tradeEvent);
    }

    /**
     * 获取所有
     */
    @Override
    public List<TradeRecord> findAll() {
        return jpaTradeRepository.findAll();
    }
}


  • La capa de infraestructura es la dirección de salida de los datos, que incluye principalmente la implementación técnica de la base de datos, caché, cola de mensajes, acceso remoto, etc.
  • La capa de diseño básico oculta los detalles de implementación técnica desde el exterior y proporciona servicios de salida de datos de grano grueso
  • Operación de la base de datos: la capa de dominio transmite objetos de datos, que se pueden dividir e implementar de acuerdo con la implementación de la tabla de datos

6. Capa de interfaz de usuario (controlador)

@RequestMapping("/trade")
@RestController
public class TradeController {

    @Autowired
    private TradeManager tradeManager;

    @Autowired
    private TradeRepository tradeRepository;

    @PostMapping(path = "/recharge")
    public TradeDTO recharge(@RequestBody TradeDTO tradeDTO) {
        return TradeDTO.toDto(tradeManager.recharge(tradeDTO.toEntity()));
    }

    @PostMapping(path = "/consume")
    public TradeDTO consume(@RequestBody TradeDTO tradeDTO) {
        return TradeDTO.toDto(tradeManager.consume(tradeDTO.toEntity()));
    }

    @GetMapping(path = "/{tradeNumber}")
    public TradeDTO findByTradeNumber(@PathVariable("tradeNumber") String tradeNumber){
        return TradeDTO.toDto(tradeRepository.findByTradeNumber(tradeNumber));
    }

}



  • La capa de interfaz de usuario proporciona soporte de servicio al terminal.
  • De acuerdo con diferentes escenarios, un módulo separado puede proporcionar http restful para la Web y soporte RPG para llamadas API entre servicios
  • Proporcionar servicios de autenticación de identidad y verificación de autoridad para la Web, conversión de datos VO
  • Proporcionar servicios de fusión y limitación de corriente para el lado API, conversión de datos DTO
  • La conversión de datos de la capa de aplicación a la capa de interfaz de usuario es más conveniente para cambios en los requisitos antes de diferentes escenarios, al tiempo que garantiza la uniformidad del formato de datos de la capa de aplicación.

7. Consulta de datos complejos

De lo anterior se puede ver que no hay un problema complicado de consulta de datos, este problema no involucra el procesamiento de la lógica empresarial, por lo que no debe manejarse en la capa de dominio.

Si hay requisitos de consulta de datos más complejos, se puede utilizar el modo CQRS y la consulta será procesada por un solo módulo. Si se pueden realizar menos consultas de datos en la capa de infraestructura, encapsulación de datos en la capa de aplicación y llamada de datos en la capa de interfaz de usuario

  • JPA no es adecuado para operaciones de consulta de bases de datos relacionadas con múltiples tablas, se pueden usar otras arquitecturas ORM flexibles

En el caso de big data y gran concurrencia, la asociación de múltiples tablas afectará seriamente el rendimiento de la base de datos, puede considerar la consulta de tabla amplia

3. Resumen

Las capas DDD resuelven principalmente el problema del acoplamiento entre cada capa, de modo que cada capa no se afecte entre sí. En cada capa, el diseño de la capa de dominio es el centro de todo el sistema y refleja mejor la idea central del diseño impulsado por el dominio. Su buen diseño es asegurar la sostenibilidad y mantenibilidad de la arquitectura futura.

Enlace original: https://my.oschina.net/barryhome/blog/4913300

Si cree que este artículo es útil para usted, puede seguir mi cuenta oficial y responder a la palabra clave [Entrevista] para obtener una compilación de los puntos de conocimiento básicos de Java y un paquete de regalo para la entrevista. Hay más artículos técnicos de productos secos y materiales relacionados para compartir, ¡que todos aprendan y progresen juntos!

Supongo que te gusta

Origin blog.csdn.net/weixin_48182198/article/details/112863266
Recomendado
Clasificación