Учитывая дизайн ваших примеров, вы столкнетесь с тем, что мне нравится называть адским ад. Это, безусловно, возможность спуститься по маршруту, который вы принимаете, но это приведет к высокоразвитой архитектуре, которая, вероятно, будет очень трудно поддерживать и реорганизовать. Тем не менее, если вы немного абстрагируетесь, вы можете упростить свою архитектуру, организовать обязанности немного больше и разделить проблемы таким образом, чтобы управлять вашими зависимостями было намного проще.
Службы 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, могут быть написаны, которые составляют User, Assignment и Location и/или другие объекты по-разному, чтобы соответствовать различным бизнес-правилам и решать различные проблемы.
- Даже услуги более высокого уровня могут быть написаны выше UserManagementService и аналогичных сервисов, составив их аналогичным образом, создавая даже более высокие рабочие процессы с минимальными усилиями.
Если вы внимательно относитесь к тому, как вы разрабатываете и пишете каждый уровень услуг, вы можете обеспечить множество уровней различной ответственности, сложности и способности к сшиванию. Приложение становится меньше о написании бизнес-правил и о создании частей для создания бизнес-поведения. Если часть должна быть написана, ее обычно можно записать, составив другие части и, возможно, добавив небольшое количество дополнительного поведения. Построение приложения становится намного проще, и гораздо проще создавать полностью функциональные, автономные, многоразовые части, которые легче тестировать изолированно и проще развертывать.
Вы также достигли Service-Orientation в самом прямом смысле (в соответствии с Thomas Erl в любом случае), а также все его преимущества. ;)
Спасибо, что это начинает иметь смысл. У меня был еще один вопрос. Могу ли я также создать объект UserManagementDal, который будет схожим с объектами Dal? – zSynopsis
Вы должны явно избегать создания UserManagementDal. Обратите внимание на разницу в именах различных типов услуг: Entity vs. Task vs. Orchestration. Служба сущности отвечает за операции CRUD объекта и, как таковая, отвечает за связь с хранилищем данных. Однако задача, оркестровка, приложение и другие службы НИКОГДА не должны обращаться к базе данных ... только к другим службам. Все это сводится к ответственности и отделяет эти обязанности от разных услуг. Сущности ответственны за взаимодействие хранилища данных низкого уровня. – jrista
UserManagementService - это то, что Томас Эрл вызывает службу задач. Он отвечает за «задачу» более высокого уровня для создания пользователя. Обратите внимание на разницу в виде поведения службы задачи и службы сущности. Служба сущности обеспечивает CRUD низкого уровня, в то время как задача обеспечивает более сложное поведение на уровне более высокого уровня. Сервисы оркестровки (или бизнес-сервисы рабочего процесса) еще более высокого уровня, составляя множество сервисов задач и сущностей даже в более широких областях поведения.Службы приложений будут составлять orch, task и, возможно, службы сущностей для выполнения конкретных приложений. – jrista