Esta es una transcripción más bien formada de mi comentario inicial bajo su pregunta. Las respuestas a las preguntas abordadas por el OP se pueden encontrar al final de esta respuesta. También revise la nota importante ubicada en el mismo lugar.
Lo que estás describiendo actualmente, Sipo, es un patrón de diseño llamado registro activo . Como con todo, incluso este ha encontrado su lugar entre los programadores, pero se ha descartado en favor del repositorio y los patrones del mapeador de datos por una simple razón, la escalabilidad.
En resumen, un registro activo es un objeto que:
- representa un objeto en su dominio (incluye reglas comerciales, sabe cómo manejar ciertas operaciones en el objeto, como si puede o no puede cambiar un nombre de usuario, etc.),
- sabe cómo recuperar, actualizar, guardar y eliminar la entidad.
Abordas varios problemas con tu diseño actual y el problema principal de tu diseño se aborda en el último, sexto punto (último pero no menos importante, supongo). Cuando tiene una clase para la que está diseñando un constructor y ni siquiera sabe qué debe hacer el constructor, la clase probablemente esté haciendo algo mal. Eso sucedió en tu caso.
Pero arreglar el diseño es bastante simple al dividir la representación de la entidad y la lógica CRUD en dos (o más) clases.
Así es como se ve su diseño ahora:
Employee
- contiene información sobre la estructura del empleado (sus atributos) y métodos sobre cómo modificar la entidad (si decide ir por el camino mutable), contiene lógica CRUD para la Employee
entidad, puede devolver una lista de Employee
objetos, acepta un Employee
objeto cuando lo desee actualizar a un empleado, puede devolver uno a Employee
través de un método comogetSingleById(id : string) : Employee
Wow, la clase parece enorme.
Esta será la solución propuesta:
Employee
- contiene información sobre la estructura del empleado (sus atributos) y métodos sobre cómo modificar la entidad (si decide seguir el camino mutable)
EmployeeRepository
- contiene lógica CRUD para la Employee
entidad, puede devolver una lista de Employee
objetos, acepta un Employee
objeto cuando desea actualizar a un empleado, puede devolver uno a Employee
través de un método comogetSingleById(id : string) : Employee
¿Has oído hablar de la separación de las preocupaciones ? No, lo harás ahora. Es la versión menos estricta del Principio de Responsabilidad Única, que dice que una clase debería tener una sola responsabilidad, o como dice el Tío Bob:
Un módulo debe tener una y solo una razón para cambiar.
Está bastante claro que si pude dividir claramente su clase inicial en dos que todavía tienen una interfaz bien redondeada, la clase inicial probablemente estaba haciendo demasiado, y así fue.
Lo bueno del patrón de repositorio es que no solo actúa como una abstracción para proporcionar una capa intermedia entre la base de datos (que puede ser cualquier cosa, archivo, noSQL, SQL, una orientada a objetos), sino que ni siquiera necesita ser una capa concreta. clase. En muchos lenguajes OO, puede definir la interfaz como real interface
(o una clase con un método virtual puro si está en C ++) y luego tener múltiples implementaciones.
Esto eleva completamente la decisión de si un repositorio es una implementación real de usted, simplemente depende de la interfaz al confiar realmente en una estructura con la interface
palabra clave. Y el repositorio es exactamente eso, es un término elegante para la abstracción de la capa de datos, es decir, el mapeo de datos a su dominio y viceversa.
Otra gran cosa de separarlo en (al menos) dos clases es que ahora la Employee
clase puede administrar claramente sus propios datos y hacerlo muy bien, porque no necesita ocuparse de otras cosas difíciles.
Pregunta 6: Entonces, ¿qué debe hacer el constructor en la Employee
clase recién creada ? Es simple. Debe tomar los argumentos, verificar si son válidos (como una edad probablemente no debería ser negativa o el nombre no debería estar vacío), generar un error cuando los datos no son válidos y si la validación pasada asigna los argumentos a variables privadas de la entidad. Ahora no puede comunicarse con la base de datos, porque simplemente no tiene idea de cómo hacerlo.
Pregunta 4: No se puede responder en absoluto, en general no, porque la respuesta depende en gran medida de lo que necesita exactamente.
Pregunta 5: Ahora que ha separado la clase hinchada en dos, puede tener varios métodos de actualización directamente en la Employee
clase, al igual changeUsername
, markAsDeceased
que manipulará los datos de la Employee
clase solamente en la memoria RAM y entonces se podría introducir un método como el registerDirty
de la Patrón de unidad de trabajo a la clase de repositorio, a través del cual le haría saber al repositorio que este objeto ha cambiado de propiedades y que deberá actualizarse después de llamar al commit
método.
Obviamente, para una actualización, un objeto requiere tener una identificación y, por lo tanto, ya estar guardado, y es la responsabilidad del repositorio detectar esto y generar un error cuando no se cumplen los criterios.
Pregunta 3: Si decide seguir el patrón de la Unidad de trabajo, el create
método será ahora registerNew
. Si no lo hace, probablemente lo llamaría en su save
lugar. El objetivo de un repositorio es proporcionar una abstracción entre el dominio y la capa de datos, por lo que le recomendaría que este método (sea registerNew
o no save
) acepte el Employee
objeto y depende de las clases que implementan la interfaz del repositorio, qué atributos deciden sacar de la entidad. Pasar un objeto completo es mejor, por lo que no necesita tener muchos parámetros opcionales.
Pregunta 2: Ambos métodos ahora serán parte de la interfaz del repositorio y no violarán el principio de responsabilidad única. La responsabilidad del repositorio es proporcionar operaciones CRUD para los Employee
objetos, eso es lo que hace (además de Leer y Eliminar, CRUD se traduce tanto en Crear como en Actualizar). Obviamente, podría dividir el repositorio aún más al tener un EmployeeUpdateRepository
etcétera, pero eso rara vez es necesario y una sola implementación generalmente puede contener todas las operaciones CRUD.
Pregunta 1: Terminaste con una Employee
clase simple que ahora (entre otros atributos) tendrá id. Si la identificación está llena o vacía (o null
) depende de si el objeto ya se ha guardado. No obstante, una identificación sigue siendo un atributo que posee la entidad y la responsabilidad de la Employee
entidad es cuidar sus atributos, por lo tanto, cuidar su identificación.
Si una entidad tiene o no una identificación, generalmente no importa hasta que intente hacer algo de lógica de persistencia en ella. Como se menciona en la respuesta a la pregunta 5, es responsabilidad del repositorio detectar que no está tratando de salvar una entidad que ya se ha guardado o que está tratando de actualizar una entidad sin una identificación.
Nota IMPORTANTE
Tenga en cuenta que, aunque la separación de las preocupaciones es excelente, en realidad diseñar una capa de repositorio funcional es un trabajo bastante tedioso y, en mi experiencia, es un poco más difícil de corregir que el enfoque de registro activo. Pero terminará con un diseño que es mucho más flexible y escalable, lo que puede ser algo bueno.
Employee
objeto para proporcionar abstracción, las preguntas 4. y 5. generalmente no tienen respuesta, dependen de sus necesidades, y si separa la estructura y las operaciones CRUD en dos clases, entonces está bastante claro, el constructor de laEmployee
no puede obtener datos de db nunca más, así que eso responde 6.