Búsqueda de sitios web

javascript - ¿Cómo implementar un repositorio en arquitectura limpia?


Estoy siguiendo la arquitectura limpia del Tío Bob para mi aplicación médica API, pero estoy teniendo problemas lógicos sobre dónde implementar ciertas cosas.

En mi capa de aplicación, tengo mi CreateTreatmentPlanUseCase donde inyecto dos repositorios: AbstractPatientRepo y AbstractTreatmentPlanRepo.

export class CreateTreatmentPlanUseCase implements UseCase<CreateTreatmentPlanRequestDto, Promise<Response>> {
    protected patientRepository: AbstractPatientRepository
    protected treatmentPlanRepository: AbstractTreatmentPlanRepository

    constructor(
        patientRepository: AbstractPatientRepository,
        treatmentPlanRepository: AbstractTreatmentPlanRepository
    ) {
        this.patientRepository = patientRepository
        this.treatmentPlanRepository = treatmentPlanRepository
    }
}

En mi capa de adaptadores de interfaz tengo un controlador en el directorio web

export default class CreateTreatmentPlanController {
    protected useCase: CreateTreatmentPlanUseCase

    constructor(useCase: CreateTreatmentPlanUseCase) {
        this.useCase = useCase
    }

    public async execute(req: express.Request, res: express.Response) {
        const dto = <CreateTreatmentPlanRequestDto>req.body

        const treatmentPlanOrError = await this.useCase.execute(dto)

        if (treatmentPlanOrError.isFailure()) {

        }
    }
}

Entonces necesito inyectar CreateTreatmentPlanUseCase en mi CreateTreatmentPlanController.

  1. ¿Dónde necesito crear CreateTreatmentPlanUseCase y CreateTreatmentPlanController? ¿Esto todavía sucede en mi capa adaptadores de interfaz o sucede en la capa frameworks/infrastructure?

Respuesta #1

¿Dónde necesito crear CreateTreatmentPlanUseCase y CreateTreatmentPlanController? ¿Esto todavía sucede en mi capa de adaptador de interfaz o sucede en la capa de marcos/infraestructura?

Sí, es la capa de infraestructura. Dado que estás usando JavaScript, especialmente TypeScript, deberías crear un módulo js para configurar los casos de uso. Este módulo luego puede importar el/los módulo(s) de definición de casos de uso.

Por ejemplo, yo crearía un módulo llamado usecases.js, o usecases.ts en tu caso. En la parte superior de este módulo verás importaciones como estas

import { CreateTreatmentPlanUseCase } from 'CreateTreatmentPlanUseCase'
import { PatientRepositoryImpl } from 'PatientRepositoryImpl'
import { TreatmentPlanRepositoryImpl } from 'TreatmentPlanRepositoryImpl'

Luego puedes configurar tu caso de uso. Por ejemplo.

const patientRepository = new PatientRepositoryImpl();
const treatmentRepository = new TreatmentPlanRepositoryImpl();

export const createTreatmentPlanUseCase = new CreateTreatmentPlanUseCase(
                                            patientRepository,
                                            treatmentRepository
                                            );

Una vez que tengas un módulo que haga la configuración, puedes importarlo en tu módulo de controlador. Por ejemplo.

import { createTreatmentPlanUseCase } from 'usecases' 

Tal vez organices tus módulos de una manera ligeramente diferente. Por ejemplo, todos los repositorios están en un módulo js y no en diferentes módulos como mostré. Pero creo que el concepto principal será el mismo. Deberías hacer la configuración en otro módulo y luego la definición. Esto a menudo se mezcla en JavaScript y también hay razones por las que los desarrolladores lo hacen. Pero si quieres, puedes separar estas preocupaciones.

EDITAR

Actualmente mis casos de uso están en el directorio de la aplicación (capa) y los controladores en el directorio de adaptadores (capa). Estoy usando Sequelize para mi base de datos y también estoy creando las implementaciones del repositorio en la capa de adaptadores. ¿Es esto correcto? ¿O las implementaciones del repositorio van a la capa de infraestructura/base de datos?

Está bien en la capa de adaptador. Pero no crearía un directorio llamado adapters y pondría todos los controladores y repositorios allí. Yo crearía un directorio controllers y repositories. Un enfoque aún mejor sería crear directorios por función y colocar módulos allí que se llamen repository.ts, controller.ts y usecase.ts. Por ejemplo.

 +- package.json
 |
 +- treatments
    |
    +- create
       |
       +- usecase.ts
       |
       +- repository.ts
       |
       +- controller.ts

Esto proporcionaría un mejor enfoque en las características que en los detalles técnicos como la capa de adaptadores.

Tendría un directorio de controladores donde tendría una carpeta por caso de uso. Importaría los repositorios allí, construiría el caso de uso y lo pasaría al controlador. ¿Es esto correcto?

Creo que esto está bien, ya que tus controladores están orientados a casos de uso. El beneficio sería una buena encapsulación. Mientras el módulo del controlador no exporte los casos de uso, otros no podrán utilizarlos. Es como el enfoque de paquete por funcionalidad que Simon Brown propone en el libro clean architecture de Robert C. Martin. En JavaScript es quizás una especie de paquete por módulo.

En una situación así, la estructura podría verse así:

 +- package.json
 |
 +- treatments
    |
    +- create.ts // use cases, repositories, controllers, etc.

EDITAR

Una última pregunta: si elijo el paquete por característica y necesito referenciar PatientRepository, ¿está bien si importo esa interfaz desde el directorio/paquete Patient?

Puedes hacer esto, pero normalmente aplicas el principio de segregación de interfaces y creas repositorios que están impulsados por funciones.

Por ejemplo, si tengo un caso de uso llamado CreateTreatmentPlanUseCase, creo un repositorio llamado CreateTreatmentPlanRepository. Este repositorio solo contiene los métodos requeridos por el caso de uso. Por supuesto, esto puede generar código duplicado, pero el beneficio es que los casos de uso están desacoplados. Supongo que es una compensación.

Puedes ir más allá y crear un módulo de repositorio común que utilicen ambos repositorios de casos de uso. Ahora podrías pensar que esto es lo mismo que el enfoque de PatientRepository, pero realmente no lo es. La pequeña diferencia es que haces explícitos los métodos comunes al ponerlos en un módulo común. Esto puede evitar que hagas accidentalmente un cambio que solo concierne a un caso de uso y, si miras el repositorio del caso de uso, te concentras en los métodos que el caso de uso utiliza.

Así que finalmente depende de ti tomar una decisión y refactorizar cuando tu decisión ya no parezca adecuada. A veces incluso necesitas ir hacia adelante y hacia atrás para reconocer cuál es el mejor enfoque para ti.

Respuesta #2

En la Arquitectura Limpia, la "composición" de la aplicación ocurre en el "componente principal". Este es el lugar donde conectamos todos esos detalles y realizamos la inyección de dependencias.

Puedes considerar este componente como parte de la capa de infraestructura o como la capa más externa de la arquitectura completa. Algunos incluso sugieren tener un proyecto dedicado para eso: https://medium.com/@gsferreira/the-missing-project-that-fixes-everything-in-net-5ca5ba35c6ae

En un programa CLI, esto probablemente sería el punto de entrada "main". En una aplicación web, probablemente sería el lugar donde configuras el marco web como "Startup.cs" en Asp.Net.