Resumen
¿Debería implementarse la autorización en CQRS / DDD por comando / consulta o no?
Estoy desarrollando por primera vez una aplicación en línea utilizando más o menos estrictamente el patrón DDD CQRS. Me topé con algún problema, que realmente no puedo entender.
La aplicación que estoy creando es una aplicación de libro mayor que permite a las personas crear libros mayores, así como también que otras personas puedan verlos, editarlos o eliminarlos, como los empleados. El creador de un libro mayor debería poder editar los derechos de acceso del libro mayor que creó. Incluso podría cambiar la propiedad. El dominio tiene dos agregados TLedger y TUser .
Leí muchas publicaciones con la palabra clave DDD / CQRS sobre seguridad, autorización, etc. La mayoría de ellas declararon que la autorización era un Subdominio genérico , a menos que uno estuviera creando una aplicación de seguridad.
En este caso, el dominio central es ciertamente un dominio contable interesado en transacciones, saldos y cuentas. Pero también se requiere la funcionalidad de poder administrar el acceso de grano fino a los libros de contabilidad. Me pregunto cómo diseñar esto en términos DDD / CQRS.
En los tutoriales de DDD se afirma que los comandos son parte del lenguaje omnipresente. Son significativos. Son acciones concretas que representan la "cosa real".
Debido a que todos esos comandos y consultas son acciones reales que los usuarios ejecutarían en la "vida real", ¿la implementación de la autorización debería estar asociada con todos estos "comandos" y "consultas"? Un usuario tendría autorización para ejecutar TLedger.addTransaction () pero no TLedger.removeTransaction (), por ejemplo. O, un usuario podría ejecutar la consulta "getSummaries ()" pero no "getTransactions ()".
Existiría un mapeo tridimensional en forma de user-ledger-command o user-ledger-query para determinar los derechos de acceso.
O, de forma desacoplada, los "permisos" denominados se registrarían para un usuario. Permisos que luego se asignarían para comandos específicos. Por ejemplo, el permiso "ManageTransactions" permitiría a un usuario ejecutar "AddTransaction ()", "RemoveTransaction ()", etc.
Usuario de mapeo de permisos -> libro mayor -> comando / consulta
Usuario de mapeo de permisos -> libro mayor -> permiso -> comando / consulta
Esa es la primera parte de la pregunta. O, en resumen, ¿se debe implementar la autorización en CQRS / DDD por comando o por consulta? O, ¿se debe desacoplar la autorización de los comandos?
En segundo lugar, en relación con la autorización basada en permisos. Un usuario debe poder administrar los permisos en sus Ledgers o en los Ledgers que se le ha permitido administrar.
- Los comandos de administración de autorización se producen en el Libro mayor
Pensé en agregar los eventos / comandos / controladores en el agregado de Ledger , como grantPermission (), revokePermission (), etc. En este caso, la aplicación de esas reglas sucedería en los controladores de comandos. Pero esto requeriría que todos los comandos incluyan la identificación del usuario que emitió ese comando. Luego, me registraría en el TLedger si existe el permiso para que ese usuario ejecute ese comando.
Por ejemplo :
class TLedger{
function addTransactionCmdHandler(cmd){
if (!this.permissions.exist(user, 'addTransaction')
throw new Error('Not Authorized');
}
}
- Comandos de gestión de autorizaciones en el Usuario
La otra forma sería incluir los permisos en el TUser. Un usuario de T tendría un conjunto de permisos. Luego, en los controladores de comandos de TLedger, recuperaría al usuario y comprobaría si tiene permiso para ejecutar el comando. Pero esto requeriría que busque el agregado TUser para cada comando TLedger.
class TAddTransactionCmdHandler(cmd) {
this.userRepository.find(cmd.userId)
.then(function(user){
if (!user.can(cmd)){
throw new Error('Not authorized');
}
return this.ledgerRepository.find(cmd.ledgerId);
})
.then(function(ledger){
ledger.addTransaction(cmd);
})
}
- Otro dominio con servicio
Otra posibilidad sería modelar completamente otro dominio de autorización. Este dominio estaría interesado en derechos de acceso, autorización, etc. El subdominio de contabilidad usaría un servicio para acceder a este dominio de autorización en forma de AuthorizationService.isAuthorized(user, command)
.
class TAddTransactionCmdHandler(cmd) {
authService.isAuthorized(cmd)
.then(function(authorized){
if (!authorized) throw new Error('Not authorized');
return this.ledgerRepository.find(cmd.ledgerId)
})
.then(function(){
ledger.addTransaction(cmd);
})
}
¿Qué decisión sería la forma más "DDD / CQRS"?
fuente
Respuestas:
Para la primera pregunta, he estado luchando con algo similar. Cada vez me inclino más hacia un esquema de autorización en tres fases:
1) Autorización en el nivel de comando / consulta de "¿este usuario alguna vez tiene permiso para ejecutar este comando?" En una aplicación MVC, esto probablemente podría manejarse a nivel de controlador, pero estoy optando por un controlador previo genérico que consultará el almacén de permisos en función del usuario actual y el comando de ejecución.
2) ¿La autorización dentro del servicio de aplicación de "este usuario" alguna vez * tiene permiso para acceder a esta entidad? "En mi caso, esto probablemente terminará siendo una verificación implícita simplemente por medio de filtros en el repositorio; en mi dominio esto es básicamente un TenantId con un poco más de granularidad de OrganizationId.
3) La autorización que se basa en propiedades transitorias de sus entidades (como el Estado) se manejaría dentro del dominio. (Ej. "Solo ciertas personas pueden modificar un libro de contabilidad cerrado".) Estoy optando por poner eso dentro del dominio porque depende en gran medida del dominio y la lógica comercial y no me siento realmente cómodo exponiéndolo en otros lugares.
Me encantaría escuchar las respuestas de los demás a esta idea: destrúyala si lo desea (solo proporcione algunas alternativas si lo hace :))
fuente
Implementaría la autorización como parte de su autorización BC pero la implementaría como un filtro de acción en su sistema Ledger. De esta manera, se pueden desacoplar lógicamente entre sí (su código de Ledger no debería tener que llamar al código de autorización), pero aún así obtendrá una autorización en proceso de alto rendimiento de cada solicitud entrante.
fuente