В ситуации, когда у вас есть какой-то процесс, для выполнения которого требуется несколько команд / сервисных вызовов, тогда это идеальный кандидат на DomainService.
Служба DomainService по определению имеет некоторое знание предметной области и используется для облегчения процесса, который взаимодействует с несколькими агрегатами / службами.
В этом случае я бы хотел, чтобы ваше действие контроллера вызвало команду CQRS Command / CommandHandler. Этот CommandHandler примет доменную службу как одну зависимость. Тогда CommandHandler несет единоличную ответственность за вызов метода Domain Service.
Это означает, что ваш процесс CreateTenancy содержится в одном месте, DomainService.
Обычно мои CommandHandlers просто вызывают в сервисные методы. Поэтому DomainService может вызывать несколько служб для выполнения своих функций, а не вызывать несколько CommandHandlers. Я рассматриваю обработчики команд как фасад, через который мои контроллеры могут получить доступ к домену.
Когда речь идет о разрешениях, я обычно сначала решаю, является ли авторизация пользователей для выполнения процесса проблемой Домена. Если это так, я обычно создаю интерфейс для описания прав пользователей. А также, я обычно создаю интерфейс для этого конкретного ограниченного контекста, в котором я работаю. Так что в этом случае у вас может быть что-то вроде:
public interface ITenancyUserPermissions
{
bool CanCreateTenancy(string userId);
}
Я бы тогда использовал интерфейс ITenancyUserPermission как зависимость в моем CommandValidator:
public class CommandValidator : AbstractValidator<Command>
{
private ITenancyUserPermissions _permissions;
public CommandValidator(ITenancyUserPermissions permissions)
{
_permissions = permissions;
RuleFor(r => r).Must(HavePermissionToCreateTenancy).WithMessage("You do not have permission to create a tenancy.");
}
public bool HavePermissionToCreateTenancy(Command command)
{
return _permissions.CanCreateTenancy(command.UserId);
}
}
Вы сказали, что разрешение на создание Арендатора зависит от разрешения на выполнение других задач / команд. Эти другие команды будут иметь свой собственный набор интерфейсов разрешений. И затем, в конечном счете, в вашем приложении вы сможете реализовать такие интерфейсы, как:
public class UserPermissions : ITenancyUserPermissions, IBlah1Permissions, IBlah2Permissions
{
public bool CanCreateTenancy(string userId)
{
return CanBlah1 && CanBlah2;
}
public bool CanBlah1(string userID)
{
return _authService.Can("Blah1", userID);
}
public bool CanBlah2(string userID)
{
return _authService.Can("Blah2", userID);
}
}
В моем случае я использую систему ABAC, где политика хранится и обрабатывается как файл XACML.
Использование описанного выше метода может означать, что у вас немного больше кода и несколько интерфейсов разрешений, но это означает, что любые определяемые вами разрешения относятся к ограниченному контексту, в котором вы работаете. Я чувствую, что это лучше, чем иметь интерфейс IUserPermissions всей доменной модели, который может определять методы, которые не имеют отношения к делу и / или сбивают с толку в ограниченном контексте Tenancy.
Это означает, что вы можете проверять права пользователей в ваших экземплярах QueryValidator или CommandValidator. И, конечно же, вы можете использовать реализацию интерфейсов IPermission на уровне пользовательского интерфейса, чтобы контролировать, какие кнопки / функции и т. Д. Отображаются пользователю.