Práticas recomendadas de arquitetura em camadas DDD

Quando ainda está em um único aplicativo, é uma arquitetura em camadas. Usamos a arquitetura de três camadas mais. Agora é a era dos microsserviços. Existem vários modelos de arquitetura de microsserviço comumente usados, como: arquitetura limpa, CQRS (separação de consulta de comando) e arquitetura hexagonal. Cada modelo de arquitetura tem seu próprio cenário de aplicação, mas seu núcleo é o princípio de " alta coesão e baixo acoplamento ". Usando o conceito de design orientado a domínio (DDD) para lidar com o impacto das mudanças diárias aceleradas de negócios na arquitetura, os limites da arquitetura estão se tornando mais claros e cada um desempenha suas próprias funções, o que também está alinhado com a filosofia de design de arquitetura de microsserviço. A arquitetura em camadas baseada no conceito de Domain Driven Design (DDD) se tornou o método de prática recomendada para a prática de arquitetura de microsserviço.

1. O que é arquitetura em camadas DDD

1. Arquitetura tradicional de três camadas

Para entender a arquitetura em camadas do DDD, primeiro entenda a arquitetura tradicional de três camadas.

Processo tradicional de arquitetura de três camadas:

  • A primeira etapa é considerar o design do banco de dados, como construir a tabela de dados, como projetar a relação entre as tabelas
  • A segunda etapa é construir uma camada de acesso a dados, como escolher uma estrutura ORM ou unir operações SQL
  • A terceira etapa é a realização da lógica de negócios. Como projetamos o banco de dados primeiro, todo o nosso pensamento girará em torno do banco de dados, pensando em como escrever dados para escrevê-los corretamente no banco de dados. Neste momento, a prática padrão de CRUD. Não há muita consideração sobre orientação a objetos e desacoplamento. Esse código é naturalmente cada vez mais difícil para a manutenção diária
  • A quarta etapa representa a saída da camada principalmente para usuários

2. Arquitetura em camadas DDD

A fim de resolver o problema de alto acoplamento e facilmente lidar com mudanças futuras no sistema, propusemos o conceito de uso de design orientado a domínio para projetar a arquitetura.

Este parágrafo resume parcialmente os pensamentos após a leitura "07 | DDD Layered Architecture: Effectively Reduce the Dependence between Layers" do "DDD Practice Course" de Ou Chuangxin

1) Camada de domínio

Em primeiro lugar, vamos deixar de lado o problema do banco de dados, começar com a lógica de negócios e não mais considerar a implementação do banco de dados ao projetar. Divida a camada de lógica de negócios (BLL) anterior em uma camada de domínio e uma camada de aplicativo.

A camada de domínio se concentra na realização da lógica de negócios dos objetos de negócios, refletindo as mudanças lógicas dos negócios do mundo real. É usado para expressar conceitos de negócios, status de negócios e regras de negócios. Para análise de negócios, você pode consultar: "Analisar negócios usando design orientado por domínio"

2) Camada de aplicação

A camada de aplicação é a camada superior da camada de domínio, contando com a camada de domínio, é a coordenação e orquestração de agregações e, em princípio, não inclui nenhuma lógica de negócios. Ele fornece suporte para a interface front-end com um fechamento mais granulado. Além de fornecer chamadas de nível superior, também pode incluir inscrição de eventos e mensagens.

3) Camada de interface do usuário

A camada de interface do usuário é orientada para a interface de entrada de dados para acesso do usuário e pode fornecer diferentes implementações de interface do usuário de acordo com diferentes cenários. Os serviços orientados para a Web podem ser fornecidos no modo http restful, que pode adicionar funções como autenticação de segurança, verificação de permissão e registro; serviços orientados para microsserviços podem ser fornecidos no modo RPC, e funções como limitação e fusão de corrente podem ser adicionado.

4) Camada de infraestrutura

A camada de infraestrutura é a interface de saída de dados, encapsulando os detalhes técnicos da invocação de dados. Ele pode fornecer serviços para qualquer outra camada, mas para resolver o problema de acoplamento, o princípio da inversão de dependência é adotado. As outras camadas contam apenas com a interface da infraestrutura e são separadas da implementação específica.

Dois, implementação de código em camadas DDD

1. Modelo estrutural

2. Estrutura do diretório

.
├── 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



Essa estrutura é uma estrutura simples de um único microsserviço, com todas as camadas no mesmo módulo.

No processo de desenvolvimento de projeto em grande escala, a fim de alcançar o controle de autoridade do módulo principal ou melhor flexibilidade, a estrutura pode ser ajustada de forma adequada, consulte a estrutura do sistema "Sistema de Carteira Digital"

3. Realização da camada de domínio (domínio)

Após a análise de negócios ("Análise de Negócios Usando Design Orientado a Domínio"), comece a escrever o código. O primeiro é escrever a camada de domínio, criar objetos de domínio e interfaces de serviço de domínio

1) Objeto de domínio

Os objetos de domínio aqui incluem objetos de entidade e objetos de valor.

Objeto de entidade : um objeto que possui um identificador único, pode existir sozinho e pode ser alterado

Objeto de valor : um objeto que não pode existir sozinho ou existe sozinho no nível lógico, sem sentido e imutável

Agregação : uma coleção de vários objetos, externamente como um todo

Raiz de agregação : o objeto de entidade na agregação que pode representar toda a operação de negócios, por meio da qual as operações de acesso externo são fornecidas, mantém a consistência dos dados na agregação e é o gerenciador dos objetos na agregação

Exemplo 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;

}



A partir do design do sistema do exemplo de transação da carteira , qualquer operação da carteira, como recarga, mensagem, etc., conduz a alteração do saldo da carteira por meio do objeto de transação

  • O objeto de transação e o objeto de carteira são objetos de entidade e formam um relacionamento de agregação. O objeto de transação é a raiz de agregação do modelo de negócios de transação de carteira, representando a agregação para fornecer serviços de chamada externa
  • Depois de analisar o relacionamento um-para-muitos entre o objeto de transação e o objeto de carteira (@ManyToOne), JPA é usado como a arquitetura ORM . Para mais práticas de JPA, consulte >>

A modelagem de domínio aqui usa um modelo de anemia, que tem uma estrutura simples, responsabilidades únicas, bom isolamento, mas carece de ideias de design orientado a objetos. Para modelagem de domínio, consulte "Modelo de anemia e modelo de congestionamento para modelagem de domínio"

  • domainEvents () é uma implementação de publicação de evento de domínio. A função é que qualquer operação de dados do objeto de transação acionará a publicação do evento e, em seguida, cooperará com a assinatura do evento para implementar o modelo de design orientado a evento . Claro, outros implementações também são possíveis

2) Serviços de domínio

/**
 * 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);
}



Primeiro defina a interface de serviço. A definição da interface precisa seguir a operação do negócio real . Não use a lógica do programa ou a lógica do banco de dados para projetar e definir adições, exclusões e alterações.

  • A principal direção de pensamento é quais serviços o objeto de transação pode fornecer externamente. A definição desse serviço é granular e altamente coesa . Não defina alguns métodos de nível de implementação de código específicos.
  • Os parâmetros de entrada e saída da interface devem ser considerados na forma de objetos tanto quanto possível, totalmente compatíveis com várias mudanças de cena
  • O método de consulta complexo exigido pelo front-end não é definido aqui. Geralmente, a consulta não é um serviço de domínio e não há alteração de dados e pode ser processada separadamente
  • A realização do serviço de domínio se concentra principalmente na realização lógica e não deve incluir código técnico básico, como realização de cache, realização de banco de dados, chamada remota, etc.

3) Interface de infraestrutura

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();
}



  • O principal objetivo de colocar a interface de infraestrutura na camada de domínio é reduzir a dependência da camada de domínio na camada de infraestrutura
  • O design da interface não é para expor os detalhes técnicos da implementação, como o SQL montado não pode ser usado como parâmetro

4. Implementação da camada de aplicativo (aplicativo)

// 交易服务
@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);
    }
}



Serviço de aplicativo :

  • A camada de aplicativo é uma camada muito fina, usada principalmente para chamar e combinar serviços de domínio e não deve conter nenhuma lógica de negócios
  • Pode incluir uma pequena quantidade de julgamento de parâmetros de processo
  • Uma vez que pode ser uma chamada de operação combinada de vários serviços de domínio, se houver um requisito de atomicidade, você pode adicionar controle de transação ** @ Transacional **

Assinatura do evento :

  • A assinatura de evento é uma maneira de realizar a cooperação e desacoplamento de operações em vários campos em um processo, e também é o ponto de acesso para todas as operações subsequentes no processo
  • É diferente da operação combinada de serviços de aplicativos. A combinação pode ser aumentada ou diminuída de acordo com as necessidades do cenário, mas a operação após a assinatura do evento é relativamente solidificada, principalmente para atender aos requisitos de consistência lógica

A configuração do TransactionPhase.AFTER_COMMIT é chamada após a transação da operação anterior ser concluída, reduzindo assim o impacto das operações subsequentes na operação anterior

  • As inscrições de eventos podem ter vários corpos de mensagens. Para facilitar o gerenciamento, é melhor tratá-los em uma classe.
  • A publicação de mensagens MQ é geralmente colocada na assinatura do evento

Assinatura de notícias :

  • A assinatura de mensagens é uma implementação de uma etapa de colaboração e desacoplamento entre vários microsserviços
  • O corpo da mensagem é transmitido em embalagem de objeto uniforme tanto quanto possível para reduzir a dificuldade de processamento causada pela heterogeneidade do objeto

5. Camada de infraestrutura (infraestrutura)

@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();
    }
}


  • A camada de infraestrutura é a direção de saída dos dados, que inclui principalmente a implementação técnica de banco de dados, cache, fila de mensagens, acesso remoto, etc.
  • A camada de design básica oculta detalhes técnicos de implementação de fora e fornece serviços de saída de dados de granulação grossa
  • Operação do banco de dados: A camada de domínio transmite objetos de dados, que podem ser divididos e implementados de acordo com a implementação da tabela de dados

6. Camada de interface do usuário (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));
    }

}



  • A camada de interface do usuário fornece suporte de serviço para o terminal
  • De acordo com diferentes cenários, um módulo separado pode fornecer suporte http restful para a Web e RPG para chamadas de API entre serviços
  • Fornece autenticação de identidade e serviços de verificação de autoridade para a Web, conversão de dados VO
  • Fornece serviços de limitação e fusão atuais para o lado da API, conversão de dados DTO
  • A conversão de dados da camada de aplicativo para a camada de interface do usuário é mais conveniente para mudanças nos requisitos antes de diferentes cenários, ao mesmo tempo que garante a uniformidade do formato de dados da camada de aplicativo

7. Consulta de dados complexos

Como pode ser visto acima, não existe um problema complicado de consulta de dados. Esse problema não envolve o processamento da lógica de negócios, portanto, não deve ser tratado na camada de domínio.

Se houver requisitos de consulta de dados mais complexos, o modo CQRS pode ser usado e a consulta será processada por um único módulo. Se menos consulta de dados puder ser feita na camada de infraestrutura, encapsulamento de dados na camada de aplicativo e chamada de dados na camada de interface do usuário

  • JPA não é adequado para operações de consulta de banco de dados relacionadas a várias tabelas, outras arquiteturas ORM flexíveis podem ser usadas

No caso de big data e grande simultaneidade, a associação de várias tabelas afetará seriamente o desempenho do banco de dados, você pode considerar a consulta de tabela ampla

3. Resumo

A estratificação DDD resolve principalmente o problema de acoplamento entre cada camada, de modo que cada camada não afeta uma à outra. Em cada camada, o design da camada de domínio é o centro de todo o sistema e reflete melhor a ideia central do design orientado por domínio. Seu bom design é para garantir a sustentabilidade e manutenção da arquitetura futura.

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

Se você acha que este artigo é útil para você, pode seguir minha conta oficial e responder à palavra-chave [Entrevista] para obter uma compilação de pontos de conhecimento do núcleo de Java e um pacote de presente para entrevista! Existem mais artigos técnicos e materiais relacionados para compartilhar. Deixe que todos aprendam e progridam juntos!

Acho que você gosta

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