Управление транзакциями в зависимых сервисах - PullRequest
0 голосов
/ 23 сентября 2019

Меня интересует архитектурное решение следующего момента.

У меня есть:

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 зарегистрирован как Scoped, тогда я могу удалить _repository.Commit из CreateOrganization и CreateUser ([1] и [2]).В этом случае [4] будет отвечать за принятие всех изменений.

Но что тогда делать, когда OrganizationService и UserService используются независимо?В конце концов, теперь они стали независимыми службами и не могут сохранять данные без делегирования изменений в какой-либо другой службе:

public IActionResult Post([FromServices] UserService service, [FromServices] TransactionService transaction, [FromBody] string userName){
    service.CreateUser(userName);   
    transaction.Commit();
    return Ok();
}

Насколько это решение является хорошим

Ответы [ 2 ]

2 голосов
/ 23 сентября 2019

Транзакции требуют единицы работы.Другого способа координировать репозитории нет.Причина, по которой вы здесь сталкиваетесь с проблемами, заключается в том, что весь ваш дизайн неправильный.

Прежде всего, вы вообще не должны иметь эти репозитории.Вы используете EF Core, который представляет собой ORM, и уже реализует шаблоны хранилища и единицы работы.Использование ORM решает использовать стороннюю библиотеку для вашего DAL.Оборачивать свой собственный слой DAL бессмысленно и накладывать ненужные затраты на обслуживание и тестирование приложения с выгодой ноль .Ваши сервисы должны напрямую зависеть от вашего контекста.

Тогда сервисы должны быть автономными единицами функциональности.Если они зависят от других служб, вы делаете это неправильно.Сервис должен соответствовать конкретному поддомену вашего приложения.Если необходимо управлять транзакциями между пользователями и организацией, то у вас должна быть одна служба, охватывающая обе.

В качестве альтернативы, если вы хотите / должны хранить эти две группы отдельно, вам потребуетсявключить концепцию саг .

1 голос
/ 23 сентября 2019

Итак, я начал больше двигаться к тому, что Крис упомянул в своем ответе, и напрямую использую 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.Его жизненный цикл должен обрабатываться тем, кто создал этот экземпляр.

Универсальный репозиторий также не должен иметь функции фиксации, кроме случаев, когда он явно запускает новую транзакцию.Так что теперь вам нужно иметь что-то, что обрабатывает запуск и совершение указанной транзакции.В веб-сценарии вы обычно находитесь в сеансе для каждого запроса.Это будет означать, что вы создаете сеанс в BeginRequest и удаляете его в EndRequest.Затем я использую атрибут транзакции для управления созданием транзакций до выполнения действия контроллера и фиксации / отката после выполнения метода контроллера.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...