我对接下来的架构解决方案感兴趣。
我有:
public class GenericRepository<T> : IDisposable {
public GenericRepository(ISession session){
_session = session;
};
public T InsertAsync(T entity){...};
public IQueryable<T> Read(){...};
public T UpateAsync(T entity){...};
public void DeleteAsync(T entity){...};
public Task Commit(){
return _session.Transaction.Commit();
};
public void Dispose(){
if(_session.Transaction.IsActive){
_session.Transaction.Rollback();
}
};
}
public class UserService{
public UserService(GenericRepository<User> repository){...}
public long CreateUser(string userName){
...
_repository.Commit(); // [1]
};
}
public class OrganizationService{
public OrganizationService(GenericRepository<Organization> repository){...}
public int CreateOrganization(string code){
...
_repository.Commit(); // [2]
};
}
使用以下注册:
services.AddScoped<ISession>(x => x.GetRequiredService<NHSessionProvider>().OpenSession());
services.AddScoped(typeof(GenericRepository<>));
services.AddScoped<UserService>();
services.AddScoped<OrganizationService>();
这些CreateOrganization
和CreateUser
可以在代码的任何部分中独立使用:
public IActionResult Post([FromServices] OrganizationService service, [FromBody] string code){
service.CreateOrganization(code);
return Ok();
}
public IActionResult Post([FromServices] UserService service, [FromBody] string userName){
service.CreateUser(userName);
return Ok();
}
但是,现在我有一项新服务:
public class MyBillingService{
public MyBillingService(GenericRepository<Contractor> repository, OrganizationService organizationService, UserService userService){...}
public int CreateNewContractor(string organizationCode, string userName){
...
_organizationService.CreateOrganization(organizationCode);
...
_userService.CreateUser(userName);// [3]
...
_repository.Commit(); // [4]
}
}
在此实现中,CreateOrganization
和CreateUser
拥有自己的事务,如果[3]引发异常,则无论如何都会创建组织。
好的,因为ISession
已注册为“作用域”,所以我可以从_repository.Commit
和CreateOrganization
([1]和[2])中删除CreateUser
。在这种情况下,[4]将负责提交所有更改。
但是,OrganizationService
和UserService
分别使用时该怎么办?毕竟,现在它们已成为非独立服务,并且在不将更改提交委托给其他服务的情况下无法保存数据:
public IActionResult Post([FromServices] UserService service, [FromServices] TransactionService transaction, [FromBody] string userName){
service.CreateUser(userName);
transaction.Commit();
return Ok();
}
就此决定是一个好的决定?
答案 0 :(得分:2)
交易需要一个工作单元。没有其他方法可以协调存储库。您在这里面临问题的原因是您的整个设计是错误的。
首先,您根本不应该拥有这些存储库。您正在使用EF Core(这是一个ORM),并且已经实现了存储库和工作单元单元。使用ORM选择为DAL使用第三方库。将自己的DAL层包装起来毫无意义,并给应用程序带来不必要的维护和测试成本,并带来 zero 好处。您的服务应直接取决于您的上下文。
然后,服务应该是功能齐全的独立单元。如果他们依赖其他服务,那您做错了。该服务应与您的应用程序的特定子域相对应。如果需要以事务方式一起管理用户和组织,那么您应该拥有包含两者的一项服务。
或者,如果您希望/需要将两者分开,则需要合并sagas的概念。
答案 1 :(得分:1)
因此,我已经开始朝着克里斯在他的答案中提到的方向发展,并直接使用ISession,但是过去我使用的是通用存储库。您的存储库无法正确处理已经开始的事务。
所以我的通用仓库有几种方法
protected virtual TResult Transact<TResult>(Func<TResult> func)
{
if (_session.Transaction.IsActive)
return func.Invoke();
TResult result;
using (var tx = _session.BeginTransaction(IsolationLevel.ReadCommitted))
{
result = func.Invoke();
tx.Commit();
}
return result;
}
protected virtual void Transact(System.Action action)
{
Transact(() =>
{
action.Invoke();
return false;
});
}
然后实现回购功能的方法如下
public bool Remove(T item)
{
Transact(() => _session.Delete(item));
return true;
}
如果该方法已经启动,则允许该方法使用现有的事务,否则将为该工作创建事务。
由于您不拥有对ISession的引用,因此您的回购中也不应包含Dispose。它的生命周期应该由创建该实例的人来处理。
通用存储库也不应具有提交功能,除非它明确启动了新事务。因此,现在您需要处理启动和提交所说事务的东西。在Web方案中,通常每个请求都有一个会话。这意味着您将在BeginRequest中创建会话并将其在EndRequest中处置。然后,我使用事务属性在执行控制器操作之前管理创建的事务,并在执行控制器方法之后提交/回滚。