Por que DDD não é sobre diagramas chatos, mas sobre arquitetura viva
\nQuando um projeto em C++ ultrapassa o tamanho de uma aplicação "de brinquedo", os desenvolvedores frequentemente enfrentam o caos: a lógica de negócios espalhada pelos controladores, o banco de dados ditando a estrutura das classes, e os novatos com medo de tocar no código legado. Domain-Driven Design (DDD) não é um conjunto de regras abstratas, mas uma ferramenta prática que ajuda a domar a complexidade. Neste artigo, vamos explorar como implementar DDD em C++, usando exemplos concretos de código, e por que isso salvará seu projeto da "grande bola de lama".
\n\nO que é DDD e por que um desenvolvedor C++ precisa dele?
\nDomain-Driven Design (design orientado a domínio) é uma abordagem onde o foco principal está no modelo do domínio, e não nos detalhes técnicos. Em C++, isso é especialmente relevante: a linguagem oferece ferramentas poderosas (templates, RAII, ponteiros inteligentes), mas sem uma arquitetura adequada, o projeto se transforma em um monólito onde alterar uma funcionalidade quebra tudo.
\nDDD propõe dois níveis:
\n- \n
- Modelagem estratégica: divisão do sistema em contextos (Bounded Contexts) e configuração da interação entre eles. \n
- Modelagem tática: blocos de construção — Entity, Value Object, Aggregate, Repository, Domain Service. \n
Vamos nos concentrar na modelagem tática, pois é ela que fornece padrões concretos para o código em C++.
\n\nArquitetura em camadas: o fundamento do DDD em C++
\nDDD pressupõe uma divisão clara em camadas. A estrutura típica para um projeto C++ é a seguinte:
\n- \n
- Domain Layer (núcleo): entidades, value objects, agregados, serviços de domínio. Sem dependências de frameworks ou bibliotecas externas. \n
- Application Layer: casos de uso (use cases), orquestração. Depende apenas do Domain. \n
- Infrastructure Layer: implementação de repositórios (SQL, Redis, arquivos), integração com APIs externas. Implementa interfaces do Domain. \n
- Presentation Layer: interface do usuário (console, REST API, GUI). \n
Isso não é uma regra rígida, mas uma recomendação. O principal é que as dependências apontam para dentro: camadas externas podem depender das internas, mas não o contrário.
\n\nExemplo: como NÃO fazer
\n// ❌ Ruim: lógica de negócios misturada com SQL\nclass UserController {\n void createUser(const std::string& name) {\n // Consulta SQL diretamente aqui\n db.execute("INSERT INTO users (name) VALUES (?)", name);\n // Lógica de validação também aqui\n if (name.empty()) throw std::runtime_error("Name required");\n }\n};\n\n\nExemplo: estilo DDD
\n// ✅ Bom: separação clara\nclass User : public Entity<User, int> {\n std::string name_;\npublic:\n explicit User(int id, std::string name) : Entity(id), name_(std::move(name)) {\n if (name_.empty()) throw std::invalid_argument("User name cannot be empty");\n }\n const std::string& name() const { return name_; }\n};\n\n// Interface do repositório (Domain)\nclass IUserRepository {\npublic:\n virtual ~IUserRepository() = default;\n virtual void save(const User& user) = 0;\n};\n\n// Implementação (Infrastructure)\nclass SqlUserRepository : public IUserRepository {\n void save(const User& user) override {\n // Apenas SQL, sem lógica de negócios\n }\n};\n\n\nBlocos de construção do DDD em C++
\n\nEntity (Entidade)
\nÉ um objeto com um identificador único que persiste ao longo do tempo. Em C++, é conveniente criar uma classe base template:
\ntemplate<typename T, typename IdType>\nclass Entity {\n IdType id_;\npublic:\n explicit Entity(IdType id) : id_(std::move(id)) {}\n IdType id() const { return id_; }\n bool operator==(const Entity& other) const { return id_ == other.id_; }\n