Sé que estás pensando (o tal vez gritando), "¿no es otra pregunta preguntando dónde pertenece la validación en una arquitectura en capas?!?" Bueno, sí, pero espero que esto sea un poco diferente al tema.
Creo firmemente que la validación toma muchas formas, está basada en el contexto y varía en cada nivel de la arquitectura. Esa es la base para la publicación: ayudar a identificar qué tipo de validación se debe realizar en cada capa. Además, una pregunta que a menudo surge es dónde pertenecen las verificaciones de autorización.
El escenario de ejemplo proviene de una aplicación para un negocio de catering. Periódicamente durante el día, un conductor puede entregar a la oficina cualquier exceso de efectivo que haya acumulado mientras lleva el camión de un sitio a otro. La aplicación permite al usuario registrar la "caída de efectivo" mediante la recopilación de la identificación del conductor y la cantidad. Aquí hay un código esqueleto para ilustrar las capas involucradas:
public class CashDropApi // This is in the Service Facade Layer
{
[WebInvoke(Method = "POST")]
public void AddCashDrop(NewCashDropContract contract)
{
// 1
Service.AddCashDrop(contract.Amount, contract.DriverId);
}
}
public class CashDropService // This is the Application Service in the Domain Layer
{
public void AddCashDrop(Decimal amount, Int32 driverId)
{
// 2
CommandBus.Send(new AddCashDropCommand(amount, driverId));
}
}
internal class AddCashDropCommand // This is a command object in Domain Layer
{
public AddCashDropCommand(Decimal amount, Int32 driverId)
{
// 3
Amount = amount;
DriverId = driverId;
}
public Decimal Amount { get; private set; }
public Int32 DriverId { get; private set; }
}
internal class AddCashDropCommandHandler : IHandle<AddCashDropCommand>
{
internal ICashDropFactory Factory { get; set; } // Set by IoC container
internal ICashDropRepository CashDrops { get; set; } // Set by IoC container
internal IEmployeeRepository Employees { get; set; } // Set by IoC container
public void Handle(AddCashDropCommand command)
{
// 4
var driver = Employees.GetById(command.DriverId);
// 5
var authorizedBy = CurrentUser as Employee;
// 6
var cashDrop = Factory.CreateCashDrop(command.Amount, driver, authorizedBy);
// 7
CashDrops.Add(cashDrop);
}
}
public class CashDropFactory
{
public CashDrop CreateCashDrop(Decimal amount, Employee driver, Employee authorizedBy)
{
// 8
return new CashDrop(amount, driver, authorizedBy, DateTime.Now);
}
}
public class CashDrop // The domain object (entity)
{
public CashDrop(Decimal amount, Employee driver, Employee authorizedBy, DateTime at)
{
// 9
...
}
}
public class CashDropRepository // The implementation is in the Data Access Layer
{
public void Add(CashDrop item)
{
// 10
...
}
}
He indicado 10 ubicaciones donde he visto verificaciones de validación colocadas en el código. Mi pregunta es qué verificaciones realizaría, si las hubiera, en cada una de las siguientes reglas comerciales (junto con verificaciones estándar de longitud, rango, formato, tipo, etc.):
- El monto de la caída de efectivo debe ser mayor que cero.
- La caída de efectivo debe tener un controlador válido.
- El usuario actual debe estar autorizado para agregar caídas de efectivo (el usuario actual no es el conductor).
Por favor comparta sus pensamientos, cómo tiene o cómo abordaría este escenario y los motivos de sus elecciones.
CashDropAmount
objeto de valor en lugar de usar un Decimal
. Verificar si el controlador existe o no se haría en el controlador de comandos y lo mismo ocurre con las reglas de autorización. Puede obtener una autorización de forma gratuita haciendo algo como Approver approver = approverService.findById(employeeId)
lo que arroja si el empleado no está en el rol de aprobador. Approver
solo sería un objeto de valor, no una entidad. También podría deshacerse de su método de fábrica de la fábrica o el uso de un AR lugar: cashDrop = driver.dropCash(...)
.