DDD: Creación de módulos reutilizables y distinciones de tipo de servicio (Dominio, Infraestructura, Aplicación)

8

Entonces, después de leer "Implementando el diseño impulsado por el dominio por Vaughn Vernon", he decidido refactorizar mi código para una mejor usabilidad al aislar lo que creo que son conceptos básicos del dominio en módulos separados.

Cada módulo contiene su propio conjunto de capas arquitectónicas distintas que incluyen el dominio, la infraestructura y la capa de aplicación / presentación (según la recomendación de Vaughn, he decidido separar aún más las responsabilidades de la capa de aplicación de las rutas, controladores MVC + plantillas que existen en el Capa de presentación).

He decidido colocar cada una de estas capas dentro de su propio paquete; y cada paquete hace referencia a la capa debajo de él como una dependencia. es decir: la capa de presentación depende de la capa de aplicación, la aplicación depende de la infraestructura, etc. Dado que el repositorio es parte del dominio, cada interfaz de repositorio existe dentro de la capa / paquete del dominio y la implementación es responsabilidad de la capa / paquete de infraestructura (Doctrine , etc.)

Espero que al reestructurar mi código de esta manera pueda intercambiar la capa de Aplicación y reutilizar mi Dominio en múltiples Aplicaciones Web.

El código finalmente parece que está empezando a formarse nuevamente, sin embargo, lo que aún me confunde es esta distinción entre Aplicación, Infraestructura y Servicios de dominio.

Un ejemplo común de un Servicio de dominio es algo que usaría para hacer hash de contraseñas. Esto tiene sentido para mí desde la perspectiva de SRP, ya que la entidad User no debería preocuparse por los diferentes algoritmos de hash que podrían usarse para almacenar las credenciales de un usuario.

Con eso en mente, traté este nuevo servicio de Dominio de la misma manera que mis Repositorios; definiendo una interfaz en el Dominio y dejando la implementación a la capa de Infraestructura. Sin embargo, ahora me pregunto qué debería hacerse con los Servicios de aplicación.

Tal como está ahora, cada entidad tiene su propio servicio de aplicación, es decir, la entidad de usuario tiene un servicio de usuario dentro de la capa de aplicación. El UserService en este caso es responsable de analizar los tipos de datos primitivos y manejar un caso de uso común "UserService :: CreateUser (nombre de cadena, cadena de correo electrónico, etc.): Usuario.

Lo que me preocupa es el hecho de que tendré que volver a implementar esta lógica en varias aplicaciones si decidiera cambiar la capa de aplicación. Así que supongo que esto me lleva a mis próximas preguntas:

  1. ¿Los servicios de dominio son simplemente una interfaz que existe para proporcionar una capa de abstracción entre la capa de infraestructura y su modelo? es decir: repositorios + servicios de hash, etc.

  2. Mencioné tener un Servicio de Aplicación que se ve así:

    • Access / Application / Services / UserService :: CreateUser (nombre de cadena, cadena de correo electrónico, etc.): Usuario

    • La firma del método acepta argumentos de tipo de datos primitivos y devuelve una nueva entidad de usuario (¡no un DTO!).

    ¿Pertenece esto a la capa de Infraestructura como una implementación de alguna interfaz definida dentro de la capa de Dominio o la Capa de Aplicación es de hecho más apropiada debido a los argumentos de tipo de datos primitivos, etc. ?

    ejemplo:

    Access/Domain/Services/UserServiceInterface 

    y

    Access/Infrastructure/Services/UserService implements UserServiceInterface
  3. Cómo deben manejar los módulos separados las relaciones unidireccionales. ¿Debería el módulo A hacer referencia a la capa de aplicación del módulo B (como lo está haciendo ahora) o la implementación de la infraestructura (a través de una interfaz separada)?

  4. ¿Los servicios de capa de aplicación requieren una interfaz separada? Si la respuesta es sí, ¿dónde deberían ubicarse?

usuario2308097
fuente
2
Estos son temas muy diversos. Te sugiero que dividas esto en preguntas separadas.
guillaume31

Respuestas:

7

¿Los servicios de dominio son simplemente una interfaz que existe para proporcionar una capa de abstracción entre la capa de infraestructura y su modelo? es decir: repositorios + servicios de hash, etc.

Las responsabilidades de los servicios de dominio incluyen varias cosas. Lo más obvio es la lógica de la vivienda que no cabe en una sola entidad. Por ejemplo, es posible que deba autorizar un reembolso por una compra determinada, pero para completar la operación necesita datos de la Purchaseentidad, Customerentidad, CustomerMembershipentidad.

Los servicios de dominio también pueden proporcionar las operaciones que necesita el dominio para completar su funcionalidad, por ejemplo PasswordEncryptionService, pero la implementación de este servicio residirá en la capa de infraestructura, ya que será principalmente una solución técnica.

Los servicios de infraestructura son servicios que, por lo tanto, una operación de infraestructura, como abrir una conexión de red, copiar archivos del sistema de archivos, hablar con un servicio web externo o hablar con una base de datos.

Los servicios de aplicación son la implementación de un caso de uso en la aplicación que está creando. Si cancela una reserva de vuelo, debería:

  1. Consulte la base de datos para el objeto Reserva.
  2. invocar Reserva-> cancelar.
  3. Guardar objeto en DB.

La capa de aplicación es el cliente del dominio. El dominio no tiene idea de cuál es su caso de uso. Simplemente expone la funcionalidad a través de sus agregados y servicios de dominio. Sin embargo, la capa de aplicación refleja lo que está tratando de lograr al orquestar las capas de dominio e infraestructura.

Mencioné tener un Servicio de aplicación que se ve así: Acceso / Aplicación / Servicios / UserService :: CreateUser (nombre de cadena, cadena de correo electrónico, etc.): Usuario La firma del método acepta argumentos de tipo de datos primitivos y devuelve una nueva Entidad de usuario (no un DTO !).

PHP podría no ser el mejor lugar para comenzar a aprender sobre DDD ya que muchos de los frameworks PHP (Laravel, Symfony, Zend, etc.) tienden a promover RAD. Se centran más en CRUD y en traducir formas a entidades. CRUDO! = DDD

Su capa de presentación debe ser responsable de leer las entradas del formulario del objeto de solicitud e invocar el servicio de aplicación correcto. El servicio de aplicación creará el usuario e invocará el repositorio de usuarios para guardar al nuevo usuario. Opcionalmente, puede devolver un DTO del usuario a la capa de presentación.

Cómo deben manejar los módulos separados las relaciones unidireccionales. ¿Debería el módulo A hacer referencia a la capa de aplicación del módulo B (como lo está haciendo ahora) o la implementación de la infraestructura (a través de una interfaz separada)?

El módulo de palabras en la jerga DDD tiene un significado diferente al que está describiendo. Un módulo debe contener conceptos relacionados. Por ejemplo, un módulo de pedido en la capa de dominio podría albergar el agregado de pedido, la entidad OrderItem, OrderRepositoryInterface y MaxOrderValidationService.

Un módulo Order en la capa de aplicación podría albergar OrderApplicationServie, CreateOrderCommand y OrderDto.

Si está hablando de capas, cada capa debe depender preferiblemente de las interfaces de otras capas siempre que sea posible. La capa de presentación debe depender de las interfaces de la capa de aplicación. La capa de aplicación debe hacer referencia a las interfaces de los repositorios o servicios de dominio.

Personalmente, no creo interfaces para entidades y objetos de valor porque creo que las interfaces deberían estar relacionadas con un comportamiento, pero YMMV :)

¿Los servicios de capa de aplicación requieren una interfaz separada? Si la respuesta es sí, ¿dónde deberían ubicarse?

Depende :) para aplicaciones complejas, construyo interfaces porque aplicamos pruebas rigurosas de unidad, integración y aceptación. El acoplamiento flojo es clave aquí y las interfaces están en la misma capa (capa de aplicación).

Para una aplicación simple, construyo directamente contra los servicios de la aplicación.

Songo
fuente
Estoy de acuerdo con la mayoría de lo que ha escrito, solo quiero decir que la parte RAD de los marcos ayuda mucho a la creación de prototipos de la aplicación, para desacoplar podemos confiar en el modelado de dominio, incluso si PHP no es bien conocido con esto, si tener una implementación de registro activa como capa de origen de datos, podemos considerarlo como DTO, ya que el tío Bob dijo que es otro tipo de DTO y un adaptador puede mediar entre la capa de dominio y los comandos de fuente de datos también son otro tipo de DTO, la mejor práctica es acoplar el formas de comando (DTO) no la entidad, casi todos los marcos tienen contenedor DI, así que use interfaces.
Cherif BOUCHELAGHEM
1

Hmm respuesta corta a una pregunta larga, pero veo el patrón de la siguiente manera

Regla 1: los objetos de dominio deben tener una única raíz agregada

Regla 2: las raíces agregadas no deberían ser demasiado grandes, divida las cosas en contextos limitados

Problema: las raíces agregadas pronto se vuelven demasiado grandes y no hay una forma clara de trazar una línea entre los distintos modelos de dominio en ellas

Solución: Servicios de dominio. Cree interfaces que pueda inyectar en modelos de dominio que les permitan hacer cosas fuera de su contexto de dominio o raíz agregada.

Así que creo que diría que sus ejemplos son servicios / repositorios normales, etc., IDatabaseRepositoryForStoringUsers o IGenericHashingCode

Un servicio de dominio permite la comunicación entre contextos limitados. es decir

User.UpgradeAccount(IAccountService accountService)
{
    accountService.UpgradeUsersAccount(this.Id);
}

Donde los usuarios y las cuentas están en raíces agregadas / contextos limitados separados.

Si el usuario y la cuenta están en la misma raíz agregada, por supuesto, debería poder hacer:

User.UpgradeAccount()
{
    this.MyAccount.Upgrade();
}

No estoy totalmente claro de su pregunta sobre cómo está integrando el material de la Aplicación / Infraestructura nTier y el material del Módulo. Obvias que realmente no quieres ninguna referencia cruzada entre contextos limitados, por lo que colocarías tus interfaces de servicio de dominio en su propio módulo que no hace referencia a ningún otro contexto limitado. limitarlo a exponer tipos de valores base, o tal vez solo DTO

Ewan
fuente
La integración es una preocupación del paquete / capa de aplicación de cada módulo. es decir: Service Container Dynamic Bindings + Routes. En cuanto a las relaciones entre módulos, si otro módulo separado (ProjectManagement, por ejemplo) necesita realizar algún tipo de Autenticación dentro de su propia API respectiva, entonces incluyo la capa de Aplicación del módulo "Acceso" como una dependencia listada. El administrador de paquetes recupera las dependencias anidadas restantes. Aplicación de acceso -> Infraestructuras de acceso -> Dominio de acceso para que pueda realizar el trabajo necesario.
user2308097
1
Sí, creo que ese tipo de enlace entre dominios es probablemente un error. Es probable que obtenga dependencias circulares
Ewan