Alura > Cursos de Programação > Cursos de Node.JS > Conteúdos de Node.JS > Primeiras aulas do curso DDD: aplicando padrões táticos

DDD: aplicando padrões táticos

Iniciando com padrões táticos - Apresentação

Apresentando o curso e o instrutor

Olá! Sejam muito bem-vindos ao nosso curso de aplicação de padrões táticos do Domain-Driven Design. Meu nome é Vinícius Albano, sou o instrutor deste curso.

Audiodescrição: Vinícius é um homem branco, com cabelo castanho e barba curta. Ele está em seu escritório, onde o curso está sendo gravado.

Introduzindo os conceitos do curso

Neste curso, vamos aprender a aplicar os padrões do Domain-Driven Design. A proposta é que compreendamos objetos de valor, entidades, agregados, repositórios, serviços de domínio e de aplicação. Também abordaremos noções básicas sobre eventos de domínio e como implementar uma arquitetura orientada a eventos de forma básica.

Trabalharemos com um projeto prático, um sistema de e-commerce, focando no contexto de pedidos. Vamos evoluir e construir todo o domínio até chegarmos à integração com o módulo de pagamentos. Essa integração será feita de forma assíncrona, e discutiremos os desafios que enfrentamos, como o acoplamento entre diferentes contextos delimitados. Além disso, realizaremos uma integração assíncrona orientada a eventos, demonstrando como é possível evoluir o sistema por meio dos eventos do domínio.

Recomendando o público-alvo e pré-requisitos

Este curso é recomendado para quem deseja aprender a aplicar o domínio de Event Design na prática, ou seja, durante a parte de codificação do projeto. Nós temos outros cursos orientados também a líderes técnicos e gerentes de projetos que abordam o DDD na parte mais estratégica, mas este curso é mais indicado para quem vai realizar a codificação dos projetos de verdade.

Para aproveitar este curso ao máximo, é recomendado que já tenhamos uma noção sólida de Node.js, TypeScript e que já tenhamos realizado algum projeto utilizando o Framework Nest, pois é ele que utilizaremos para a codificação. Não abordaremos o funcionamento completo do Nest, assumiremos que já temos esse conhecimento e aplicaremos os conceitos de DDD junto com o Nest.

Sugerindo cursos complementares e finalizando

Também é recomendado que já tenhamos concluído os outros dois cursos de DDD aqui na Alura, onde são abordados todos os fundamentos e a questão da modelagem estratégica. Nesses cursos, aprendemos a diferença entre os diferentes contextos delimitados, como eles funcionam e como funciona o mapa de contexto, pois utilizaremos esses conceitos ao longo deste curso.

Esperamos que aproveitem esta jornada e absorvam muito conteúdo interessante sobre como utilizar o DDD na prática com os padrões táticos. Boa jornada!

Iniciando com padrões táticos - Introdução ao projeto

Olá, dev! Esperamos que estejam bem. Estamos muito felizes por estarem conosco no nosso terceiro curso de Domain Dreaming Design. Neste curso, finalmente colocaremos a mão na massa e aprenderemos a codificar nossos padrões táticos.

Já tivemos uma introdução sobre esses padrões no nosso primeiro curso de fundamentos. No segundo curso, sobre planejamento estratégico com Domain Dreaming Design, entendemos como funcionam os agregados e por que são importantes. Agora, é interessante desenvolvermos um projeto do zero. Vamos discutir um pouco sobre cada um dos padrões, por que eles existem e como podemos, de fato, realizar a modelagem.

Configurando o ambiente no VS Code

No VS Code, temos o nosso repositório. Estamos na branch projeto base, onde temos uma estrutura inicial. Basicamente, instalamos o NestJS e fizemos algumas pequenas modificações no diretório. Criamos uma pasta de contextos, onde teremos nossos contextos delimitados.

Vocês sabem o que é isso?

Criando o contexto de pedidos

Vamos criar o contexto de orders, nosso contexto de pedidos, dentro do nosso e-commerce. Aqui, basicamente, temos nosso módulo do aplicativo padrão do NestJS. Não há nada de extraordinário, apenas consumimos nosso módulo de pedidos. Importamos nosso controller de pedidos e nosso serviço de pedidos também. Não fizemos nada além de renomear as funcionalidades.

Para começar, vamos configurar o módulo principal do nosso aplicativo, o AppModule, que irá importar o módulo de pedidos:

import { Module } from '@nestjs/common';
import { OrdersModule } from './contexts/orders/orders.module';

@Module({
  imports: [OrdersModule],
})
export class AppModule {}

Agora que configuramos o AppModule, vamos criar o OrdersModule, que irá gerenciar o nosso contexto de pedidos. Este módulo irá importar o OrdersController e o OrdersService:

import { Module } from '@nestjs/common';
import { OrdersService } from './application/orders.service';
import { OrdersController } from './infrastructure/orders.controller';

@Module({
  imports: [],
  controllers: [OrdersController],
  providers: [OrdersService],
})
export class OrdersModule {}

Definindo o serviço e o controlador de pedidos

O orderService retorna, por padrão, um getHello, que vem do NestJS. Vamos definir esse serviço agora:

import { Injectable } from '@nestjs/common';

@Injectable()
export class OrdersService {
  getHello(): string {
    return 'Hello World!';
  }
}

Na infraestrutura, temos nosso controller com um teste que executa nosso serviço de pedidos também no método getHello. Vamos criar o OrdersController para lidar com as requisições HTTP:

import { Controller, Get } from '@nestjs/common';
import { OrdersService } from '../../application/orders.service';

@Controller('orders')
export class OrdersController {
  constructor(private readonly ordersService: OrdersService) {}

  @Get()
  getHello(): string {
    return this.ordersService.getHello();
  }
}

Executando o projeto e testando a API

Se acessarmos o terminal e executarmos npm start, iniciaremos nosso projeto. No console, veremos que ele será iniciado.

npm start

Uma novidade é que podemos usar a ferramenta api.http. Através da extensão REST Client, podemos procurar por ela nas extensões do VS Code, instalá-la e fazer requisições HTTP através desse arquivo. Funciona muito bem e é bastante útil. Podemos, por exemplo, clicar em sendRequest, que enviará uma ordem para nosso servidor. Veremos que a requisição foi enviada, com cabeçalhos e tudo mais, e recebemos um "Hello World".

Para testar nossa API, podemos usar o seguinte comando HTTP:

###

GET http://localhost:3000/orders

Essa é a estrutura inicial do nosso repositório. Não há segredos, é basicamente a aplicação padrão do NestJS com algumas modificações na pasta.

Contexto do projeto no Miro

Gostaríamos de trazer um pouco do contexto deste projeto. No nosso projeto passado, no curso de DDD Estratégico, vimos o mapa de contextos. Nosso contexto de pedidos é um contexto central, muito importante, que se comunica com diversos outros contextos dentro do projeto Usedev. Decidimos trazer, neste projeto prático, a implementação desse contexto de pedidos. Vamos simular algumas interações com outros contextos delimitados, mas o principal foco será o contexto de pedidos.

Vamos modelar um carrinho de compras, um pedido, o pagamento e a integração com o contexto de precificação. Isso é o que veremos neste curso.

Detalhes do projeto

Voltando ao VS Code, temos nosso Readme. Recomendamos que o leia com atenção para entender como tudo funcionará, incluindo os comandos. É um projeto educacional em NestJS que demonstra os padrões práticos de DDD através do fluxo de gerenciamento de pedidos do e-commerce. Vamos cobrir desde a criação do carrinho, fechamento do pedido e pagamentos de forma simplificada. Não vamos integrar com gateways de pagamento, mas simular diversas interações para o aprendizado do DDD.

Teremos funcionalidades como criação do carrinho de compras, processamento do pagamento, eventos de domínio e consistência eventual com uma arquitetura orientada a eventos. Vamos cobrir técnicas como agregados, objetos de valor, entidades, repositórios, serviços de domínio e serviços de aplicação.

Falaremos sobre gateway, que é uma interface para simular ou isolar sistemas externos, e sobre a camada de corrupção. Também abordaremos outros contextos limitados, controlando as fronteiras com o padrão gateway e a camada de corrupção. Discutiremos eventos de domínio, message bus (camada de publicação de eventos) e arquitetura orientada a eventos de forma introdutória, pois teremos um curso específico sobre isso no futuro.

Concluindo a introdução

Este vídeo é uma introdução ao funcionamento do nosso repositório e ao projeto do curso. Em seguida, começaremos a implementação e colocaremos a mão na massa. Até já!

Iniciando com padrões táticos - Por que usar padrões táticos?

Introduzindo a criação de padrões táticos

Vamos então colocar a mão na massa e começar a entender como criamos esses padrões táticos. No nosso VS Code, dado que trabalharemos com Domain Design (Design de Domínio), precisamos criar nossa camada de domínio.

Começamos pela pasta de contextos, onde temos nosso módulo e nosso contexto ilimitado de pedidos, orders. Temos aqui, primeiramente, nossa camada de aplicação e nossa camada de infraestrutura. Já possuímos nosso controller, que está chamando o serviço na camada de aplicação, mas ainda não temos nenhum comportamento de fato, apenas o básico que o Nest nos trouxe.

Iniciando a codificação do agregado de carrinho de compras

Vamos começar criando uma pasta de domínio, domain, e iniciar a codificação do nosso agregado de carrinho de compras. Vamos criar um arquivo shopping_cart.ts e discutir algumas ideias. Começaremos fazendo uma interface para discutir a relevância dos padrões táticos.

interface ShoppingCart {

}

Como as pessoas costumam fazer modelagens de sistemas em geral? A maior parte dos sistemas atuais não possui a camada de domínio, apenas uma camada de infraestrutura, onde a entidade está diretamente relacionada ao banco de dados. Por exemplo, nosso shopping cart (carrinho de compras) precisará de um identificador. Teremos um cartId, que será uma string.

interface ShoppingCart {
  cartId: string;
}

Adicionando propriedades ao carrinho de compras

Nosso carrinho também terá um dono, um cliente, ou um usuário ao qual ele pertencerá. Teremos, por exemplo, um customerId, também uma string.

interface ShoppingCart {
  cartId: string;
  customerId: string;
}

Além disso, teremos um status que representará em qual estágio do ciclo de vida o carrinho se encontra atualmente, também uma string.

interface ShoppingCart {
  cartId: string;
  customerId: string;
  status: string;
}

Os itens do carrinho serão um array de objetos. Vamos definir um array de tipagem, onde teremos, por exemplo, o produtoId, que será uma string, e uma quantidade, quantity, que será um número.

interface ShoppingCart {
  cartId: string;
  customerId: string;
  status: string;
  items: Array<{
    productId: string;
    quantity: number;
  }>
}

Incorporando comportamentos ao carrinho de compras

Assim, temos uma prototipagem básica de como seria essa camada de persistência, com esses campos e tipagens.

A diferenciação do Domain of Design (Design de Domínio) é que não tratamos de entidades apenas com dados, mas com entidades ricas. As entidades possuem comportamento, não apenas dados; não será apenas um getter e setter. Precisaremos incluir o comportamento do negócio. Na nossa interface, poderíamos incluir, por exemplo, métodos de negócio, como um método create para criar o carrinho.

interface ShoppingCart {
  cartId: string;
  customerId: string;
  status: string;
  items: Array<{
    productId: string;
    quantity: number;
  }>;
  create() {}
}

E outro método addItem para adicionar itens ao carrinho.

interface ShoppingCart {
  cartId: string;
  customerId: string;
  status: string;
  items: Array<{
    productId: string;
    quantity: number;
  }>;
  create() {}
  addItem() {}
}

Expandindo funcionalidades do carrinho de compras

Nós poderíamos ter outro método aqui, getItem, onde receberíamos um parâmetro para retornar o item do nosso carrinho.

interface ShoppingCart {
  cartId: string;
  customerId: string;
  status: string;
  items: Array<{
    productId: string;
    quantity: number;
  }>;
  create() {}
  addItem() {}
  getItem() {}
}

Poderíamos ter, por exemplo, um método markAsConverted, para marcar que o nosso carrinho já foi convertido quando ele for transicionado para um pedido de fato.

interface ShoppingCart {
  cartId: string;
  customerId: string;
  status: string;
  items: Array<{
    productId: string;
    quantity: number;
  }>;
  create() {}
  addItem() {}
  getItem() {}
  markAsConverted() {}
}

Explorando melhorias na modelagem do carrinho

Tudo isso será modelado internamente no nosso agregado, na nossa entidade. Isso já funciona bem e ajuda bastante, mas também temos outros problemas.

Continuamos com tipos primitivos, como string e números na quantidade, além de um array de itens que também pode ter comportamento. Podemos, por exemplo, mudar a quantidade desses itens, adicionar um item novo ou remover um item. Seria ideal fazermos algumas modificações nessa entidade para adicionar outros padrões táticos do DDD.

Primeiramente, temos a questão dos objetos de valor. Podemos transformar nossos IDs, em vez de strings e tipos primitivos, em objetos de valor, para que tenham suas próprias regras e comportamentos. Por exemplo, nosso ID do carrinho pode ser implementado como um UUID, enquanto o ID de cliente pode ser uma string diferente, com um prefixo, em outro contexto ilimitado. Seria interessante termos objetos de valor que validem como esses IDs funcionam, como são gerados e como podemos compará-los ou realizar operações com eles.

E quanto aos status? Quais são os status válidos? Não pode ser qualquer coisa. Não é somente uma string; podemos ter um status como pendente ou convertido. Como modelamos isso? Nos nossos itens, seria ideal fazer uma transição para uma entidade própria, pois eles também terão um ciclo de vida. Teríamos um identificador do item do carrinho e métodos para alterar a quantidade, remover alguma quantidade, entre outros.

Concluindo a introdução ao design de domínio

Com isso, começaríamos a ter uma modelagem mais rica, facilitando a modelagem do nosso carrinho como um todo, com os comportamentos dos outros padrões de DDD para nos ajudar. Tendo isso em mente, vamos começar a implementar os padrões de face do DDD, sabendo que uma modelagem anêmica não funciona e que uma modelagem muito orientada a primitivos também pode dificultar um pouco. No próximo vídeo, vamos começar a implementar nosso primeiro objeto de valor. Vamos juntos!

Sobre o curso DDD: aplicando padrões táticos

O curso DDD: aplicando padrões táticos possui 243 minutos de vídeos, em um total de 47 atividades. Gostou? Conheça nossos outros cursos de Node.JS em Programação, ou leia nossos artigos de Programação.

Matricule-se e comece a estudar com a gente hoje! Conheça outros tópicos abordados durante o curso:

Aprenda Node.JS acessando integralmente esse e outros cursos, comece hoje!

Conheça os Planos para Empresas