質問
現在、このようなサービスクラスがあります
public class UserService : IUserService
{
private IAssignmentService _assignmentService;
private ILocationService _locationService;
private IUserDal _userDal;
private IValidationDictionary _validationDictionary;
public UserService(IAssignmentService assignmentService, ILocationService locationService, IValidationDictionary validationDictionary)
{
this._assignmentService = assignmentService;
this._locationService = locationService;
this._userDAL = new UserDal();
this._validationDictionary = validationDictionary;
}
.....
private void ValidateUser(IUser user)
{
if (_locationService.GetBy(user.Location.Id) == null)
_validationDictionary.AddError("....");
if (_assignmentService.GetBy(user.Assignment.Id) == null)
_validationDictionary.AddError("....");
.....
}
}
およびこのようなDALクラス
public class UserDal: IUserDal
{
private IAssignmentDal _assignmentDal;
private ILocationDAL _locationDAL
public UserDal()
{
this_assignmentDal = new AssignmentDal();
this._locationDal = new LocationDal();
}
public int AddUser(IUser user)
{
// db call and insert user
_locationDal.Add(user.Location);
_assignmentDal.Add(user.Assignment);
}
public IUser GetUser(int id)
{
..DB Call
IUser user = new User() { userData, GetLocation(dr["Location_Id"]),GetAssignment([dr["Assignment_Id"]);
return user
}
private ILocation GetLocation(int id)
{
return new LocationDal().GetById(id);
}
private IAssignment GetAssignment(int id)
{
return new AssignmentDal().GetById(id);
}
}
サービスレイヤーが他のサービスレイヤーオブジェクトと通信し、Dalが他のDalオブジェクトと通信するのは悪い設計と見なされるのではないかと思っていましたか?
事前に感謝
解決
例の設計を考えると、私が依存性地獄と呼ぶものに遭遇するでしょう。確かにあなたが取っているルートをたどるオプションですが、それは非常に結合されたアーキテクチャにつながり、メンテナンスとリファクタリングが非常に難しいでしょう。ただし、もう少し抽象化すれば、アーキテクチャを簡素化し、責任をもう少し整理し、依存関係の管理がはるかに簡単になるように懸念を分離できます。
UserService、AssignmentService、およびLocationServiceは、CRUDスタイルのサービスのように見えます。より適切な用語はエンティティサービスです。エンティティサービスは、直接のエンティティのCRUD操作に対してのみ責任を負い、他には何もしません。複数のエンティティ、エンティティの関係などを含む操作は、より大規模な操作を調整できる高レベルのサービスにプッシュできます。これらはオーケストレーションまたはタスクサービスと呼ばれます。
次のようなアプローチをお勧めします。ここでの目標は、各サービスを簡素化して、責任範囲を最小にし、依存関係を制御することです。サービス契約を簡素化して、既存のエンティティサービスの責任を軽減し、2つの新しいサービスを追加します。
// 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)
構成と依存関係に関して、ここで注意すべき重要な点が2つあります。 UserManagementServiceを追加し、その中にエンティティサービスを構成することにより、次のことが実現します。
- エンティティサービス間の結合の削除。
- 各エンティティサービスの依存関係の量の削減。
- これらは、DALおよび場合によっては共通のインフラストラクチャにのみ依存しています。
- 依存関係は一方向になりました。すべての依存関係は「下向き」であり、「水平」または「上向き」ではありません。
- この単純なルールは、手に負えない依存関係を完全に排除できる非常にクリーンなメカニズムを提供します。
- ユーザーを割り当てと場所に関連付けるルールは、エンティティから削除され、上位にプッシュされます。
- これにより、より柔軟な構成が提供され、コードの再利用が促進されます。
- UserManagementServiceのような他のサービスは、異なるビジネスルールを満たし、異なる問題を解決するために、User、Assignment、Locationおよび/または他のエンティティを異なる方法で構成するように記述できます。
- 高レベルのサービスでさえ、UserManagementServiceおよび同様のサービスの上に記述され、同様の方法で構成され、最小限の労力でより高レベルのワークフローを作成できます。
各レベルのサービスを設計および作成する方法に注意を払えば、責任、複雑さ、および構成可能性の異なる多くのレイヤーを提供できます。アプリケーションは、ビジネスルールを記述することよりも、ビジネス動作を作成するためにパーツを構成することをより重視します。パーツを作成する必要がある場合、通常は他のパーツを作成し、場合によっては追加の動作を少し追加することで作成できます。アプリケーションの構築がはるかに簡単になり、完全に機能する自己完結型の再利用可能なパーツを構築するのがはるかに簡単になります。
また、本当の意味でサービス指向を達成しているでしょう( Thomas Erl によると)、すべての利点とともに。 ;)
他のヒント
&quot; DataContext&quot;に似たモデルに従うことができます。 LINQ2SQLまたはEntityframeworkが続くモデル。つまり、「ユーザー」などのエンティティを追跡するコンテキストがあります。サービスレイヤーは、コンテキストと対話して、必要に応じてエンティティ間でクエリを実行するか、エンティティ間で操作を実行します。このようにして、「ユーザー」などの個々のエンティティをエンティティは、他の「無関係」に対する直接的な知識(結合)を持つ必要はありません。エンティティとコンテキストはエンティティをクライアントに公開します。