Учитывая дизайн ваших примеров, вы столкнетесь с тем, что я люблю называть адом зависимости. Это, безусловно, вариант пойти по выбранному вами маршруту, но это приведет к высокосвязанной архитектуре, которую, вероятно, будет очень сложно поддерживать и реорганизовать. Однако, если вы абстрагируетесь немного больше, вы можете упростить свою архитектуру, немного упорядочить обязанности и разделить задачи таким образом, чтобы управление вашими зависимостями стало намного проще.
UserService, AssignmentService и LocationService выглядят как службы в стиле CRUD. Более подходящим термином для них будет Entity Services. Служба сущности должна нести единоличную ответственность за операции CRUD непосредственной сущности и ничего больше. Операции, которые включают в себя несколько сущностей, взаимосвязи сущностей и т. Д., Могут быть переданы в сервис более высокого уровня, который может управлять крупномасштабными операциями. Их часто называют Orchestration или Task Services.
Я бы рекомендовал подход, подобный следующему. Цели здесь состоят в том, чтобы упростить каждый сервис, чтобы дать ему минимальный объем ответственности, и контролировать зависимости. Упростите свои сервисные контракты, чтобы уменьшить ответственность существующих сервисов организации, и добавьте две новые услуги:
// User Management Orchestration Service
interface IUserManagementService
{
User CreateUser();
}
// User Entity Service
interface IUserService
{
User GetByKey(int key);
User Insert(User user);
User Update(User user);
void Delete(User user);
}
// User Association Service
interface IUserAssociationService
{
Association FindByUser(User user);
Location FindByUser(User user);
void AssociateWithLocation(User user, Location location);
void AssociateWithAssignment(User user, Assignment assignment);
}
// Assignment Entity Service
interface IAssignmentService
{
Assignment GetByKey(int key);
// ... other CRUD operations ...
}
// Location Entity Service
interface ILocationService
{
Location GetByKey(int key);
// ... other CRUD operations ...
}
Процесс создания пользователя и связывания его с местоположением и назначением будет принадлежать UserManagementService, который будет составлять сервисы объектов более низкого уровня:
class UserManagementService: IUserManagementService
{
public UserManagementService(IUserService userService, IUserAssociationService userAssociationService, IAssignmentService assignmentService, ILocationService locationService)
{
m_userService = userService;
m_userAssociationService = userAssociationService;
m_assignmentService = assignmentService;
m_locationService = locationService;
}
IUserService m_userService;
IUserAssociationService m_userAssociationService;
IAssignmentService m_assignmentService;
ILocationService m_locationService;
User CreateUser(string name, {other user data}, assignmentID, {assignment data}, locationID, {location data})
{
User user = null;
using (TransactionScope transaction = new TransactionScope())
{
var assignment = m_assignmentService.GetByKey(assignmentID);
if (assignment == null)
{
assignment = new Assignment { // ... };
assignment = m_assignmentService.Insert(assignment);
}
var location = m_locationService.GetByKey(locationID);
if (location == null)
{
location = new Location { // ... };
location = m_locationService.Insert(location);
}
user = new User
{
Name = name,
// ...
};
user = m_userService.Insert(user);
m_userAssociationService.AssociateWithAssignment(user, assignment);
m_userAssociationService.AssociateWithLocation(user, location);
}
return user;
}
}
class UserService: IUserService
{
public UserService(IUserDal userDal)
{
m_userDal = userDal;
}
IUserDal m_userDal;
public User GetByKey(int id)
{
if (id < 1) throw new ArgumentException("The User ID is invalid.");
User user = null;
using (var reader = m_userDal.GetByID(id))
{
if (reader.Read())
{
user = new User
{
UserID = reader.GetInt32(reader.GerOrdinal("id")),
Name = reader.GetString(reader.GetOrdinal("name")),
// ...
}
}
}
return user;
}
public User Insert(User user)
{
if (user == null) throw new ArgumentNullException("user");
user.ID = m_userDal.AddUser(user);
return user;
}
public User Update(User user)
{
if (user == null) throw new ArgumentNullException("user");
m_userDal.Update(user);
return user;
}
public void Delete(User user)
{
if (user == null) throw new ArgumentNullException("user");
m_userDal.Delete(user);
}
}
class UserAssociationService: IUserAssociationService
{
public UserAssociationService(IUserDal userDal, IAssignmentDal assignmentDal, ILocationDal locationDal)
{
m_userDal = userDal;
m_assignmentDal = assignmentDal;
m_locationDal = locationDal;
}
IUserDal m_userDal;
IAssignmentDal m_assignmentDal;
ILocationDal m_locationDal;
public Association FindByUser(User user)
{
if (user == null) throw new ArgumentNullException("user");
if (user.ID < 1) throw new ArgumentException("The user ID is invalid.");
Assignment assignment = null;
using (var reader = m_assignmentDal.GetByUserID(user.ID))
{
if (reader.Read())
{
assignment = new Assignment
{
ID = reader.GetInt32(reader.GetOrdinal("AssignmentID")),
// ...
};
return assignment;
}
}
}
}
class UserDal: IUserDal
{
public UserDal(DbConnection connection)
{
m_connection = connection;
}
DbConnection m_connection;
public User GetByKey(int id)
{
using (DbCommand command = connection.CreateCommand())
{
command.CommandText = "SELECT * FROM Users WHERE UserID = @UserID";
var param = command.Parameters.Add("@UserID", DbType.Int32);
param.Value = id;
var reader = command.ExecuteReader(CommandBehavior.SingleResult|CommandBehavior.SingleRow|CommandBehavior.CloseConnection);
return reader;
}
}
// ...
}
class AssignmentDal: IAssignmentDal
{
public AssignmentDal(DbConnection connection)
{
m_connection = connection;
}
DbConnection m_connection;
Assignment GetByUserID(int userID)
{
using (DbCommand command = connection.CreateCommand())
{
command.CommandText = "SELECT a.* FROM Assignments a JOIN Users u ON a.AssignmentID = u.AssignmentID WHERE u.UserID = @UserID";
var param = command.Parameters.Add("@UserID", DbType.Int32);
param.Value = id;
var reader = command.ExecuteReader(CommandBehavior.SingleResult|CommandBehavior.SingleRow|CommandBehavior.CloseConnection);
return reader;
}
}
// ...
}
// Implement other CRUD services similarly
Концептуальные уровни и потоки данных / объектов, которые возникают в результате этой архитектуры, будут следующими:
Task: UserManagementSvc
^
|
-------------------------------------------------
| | | |
Entity: UserSvc UserAssociationsSvc AssignmentSvc LocationSvc
^ ^ ^ ^
| | | |
--------- - -
| | |
Utility: UserDal AssignmentDal LocationDal
^ ^ ^
| | |
---------------------------------------------
|
DB: (SQL Database)
Здесь следует отметить пару ключевых моментов, касающихся состава и зависимостей. Добавляя UserManagementService и создавая в нем службы сущностей, вы достигаете следующего:
- Устранение связи между службами сущностей.
- Сокращение объема зависимостей для каждой сущности сервиса.
- Они зависят только от своего DAL и, возможно, общей инфраструктуры.
- Зависимости теперь однонаправлены: все зависимости «нисходящие», никогда «горизонтальные» или «восходящие».
- Это простое правило обеспечивает очень простой механизм, с помощью которого можно полностью устранить недисциплинированные зависимости.
- Правила связывания пользователя с назначением и местоположением удаляются из сущностей и поднимаются выше.
- Это обеспечивает более гибкие композиции и стимулирует повторное использование кода.
- Могут быть написаны другие службы, такие как UserManagementService, которые по-разному объединяют пользователя, назначение и местоположение и / или другие объекты для удовлетворения различных бизнес-правил и решения различных проблем.
- Даже сервисы более высокого уровня могут быть написаны выше UserManagementService и аналогичных сервисов, составляя их аналогичным образом, создавая рабочие процессы даже более высокого уровня с минимальными усилиями.
Если вы осторожны в том, как вы проектируете и пишете каждый уровень услуг, вы можете предоставить много уровней разной ответственности, сложности и компоновки. Приложение становится меньше о написании бизнес-правил и больше о создании частей для создания бизнес-поведения. Если часть должна быть написана, она обычно может быть написана путем составления других частей и, возможно, добавления небольшого количества дополнительного поведения. Построение приложения становится намного проще, и гораздо проще создать полностью функциональные, автономные, повторно используемые детали, которые легче тестировать изолированно и проще в развертывании.
Вы также достигнете Сервис-Ориентации в прямом смысле (в любом случае, согласно Томасу Эрлу ) вместе со всеми его преимуществами. ;)