1

У меня есть абстрактный базовый класс для обработчиков команд/запроса:Избавление от ServiceLocator в базовом классе

public abstract class AbstractBusiness<TRequest, TResult> : IBusiness<TRequest, TResult> 
    where TResult : BaseResponse 
{ 
    public TResult Send(TRequest request) 
    { 
     TResult response = Activator.CreateInstance<TResult>(); 
     response.Success = true; 

     try 
     { 
      IConnectionProvider connectionProvider = 
       ServiceLocator.Current.GetInstance<IConnectionProvider>(); 

      using (DatabaseScope scope = DatabaseScopeManager.Instance.GetScope(
       connectionProvider, 
       DatabaseScopeType.UseExistingOrNewTX, 
       IsolationLevel.ReadCommitted)) 
      { 
       response = this.Handle(request); 
       scope.Complete(); 
      } 
     } 
     catch (Exception exc) 
     { 
      response.Success = false; 
      response.Message = exc.Message; 
     } 

     return response; 
    } 

    protected abstract TResult Handle(TRequest request); 
} 

Это, как он используется:

public sealed class AccountCreateBusiness 
    : AbstractBusiness<AccountCreateRequest, AccountCreateResponse> 
{ 
    private readonly IMessageProvider messageProvider; 

    public AccountCreateBusiness(IMessageProvider messageProvider) 
    { 
     this.messageProvider = messageProvider; 
    } 

    protected override AccountCreateResponse Handle(AccountCreateRequest request) 
    { 
     //handle request 
    } 
} 

Я пытаюсь избавиться от ServiceLocator в базовом классе. Есть ли способ ввести IConnectionProvider без необходимости изменять все производные классы, чтобы включить их конструкторы и позвонить : base(connectionProvider)? Я не могу изменить DatabaseScopeManager.

ответ

1

Есть альтернатива: Впрыск недвижимости.

КСТАТИ инъекции свойство не определяет обязательных зависимостей, а зависимости конструктора сделать это.

+1

Вложение свойств, однако, приводит к [Временному соединению] (http://blog.ploeh.dk/2011/05/24/DesignSmellTemporalCoupling/). – Steven

+0

@steven BTW, когда вы используете контейнер, есть небольшая вероятность, что в результате будут установлены зависимости, не заданные по свойствам .. это может произойти, если вы не настроили какой-то компонент, верно? –

+0

Правильно. Конструктор-инжектор дает нам * гарантию *, что объект задан правильно после его построения. Мы теряем эту способность с введением свойств. – Steven

1

Как упоминалось в Matías Fidemraizer, вы можете использовать Property Injection, который является вполне безопасным способом избежать нарушений. Недостатком является то, что зависимость свойства от необязательна, но в вашем коде IConnectionProvider есть a должно.

В долгосрочной перспективе беговой, я бы с двумя конструкторами:

  1. по умолчанию один, который еще использует Service Locator (для обратной совместимости), но помечается как [Obsolete], чтобы указать, что это 'будет удалено в следующей крупной версии.
  2. Конструктор с IConnectionProvider как обязательный зависимость. Этот должен быть рекомендован для использования и должен стать единственным, как только будут устранены все применения конструктора по умолчанию.
+0

К сожалению, это приводит к созданию нескольких шаблонов с несколькими конструкторами (https://www.cuttingedge.it/blogs/steven/pivot/entry.php?id=97). – Steven

+0

Вот почему я предложил пометить конструктор _new tempor_ default как устаревший. Это всего лишь способ погасить технический долг (автор сказал, что сейчас не вариант внесения изменений). –

+0

Я согласен с вашим решением в качестве временного исправления, поскольку при работе с устаревшим кодом каждый промежуточный шаг разрешается входить в лучшее место. Я, однако, не согласен с вашим решением с «долгосрочной перспективе», поскольку в конце концов этот конструктор должен исчезнуть. – Steven

2

Ядро проблемы заключается в существовании вашего базового класса. Вы должны избавиться от базового класса. Удаление базового класса полностью устранит вашу проблему.

Базовый класс является проблематичным, поскольку:

  • Это вызывает каждый производный тип, чтобы иметь зависимость от этого класса.
  • Это усложняет тестирование, так как все сквозные проблемы всегда выполняются.
  • Базовый класс станет большим бесконечно растущим классом; одно нарушение ответственности.

Хотя вы можете использовать инъекции собственности, чтобы избавиться от Service Locator, это не снимает указанные проблемы и даже вводит новую проблему, которая: Temporal Coupling.

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

TransactionBusinessDecorator:

public class TransactionBusinessDecorator<TRequest, TResult> 
    : IBusiness<TRequest, TResult> 
    where TResult : BaseResponse 
{ 
    private readonly IConnectionProvider connectionProvider; 
    private readonly IBusiness<TRequest, TResult> decoratee; 

    public TransactionBusinessDecorator(
     IConnectionProvider connectionProvider, 
     IBusiness<TRequest, TResult> decoratee) 
    { 
     this.connectionProvider = connectionProvider; 
     this.decoratee = decoratee; 
    } 

    public TResult Send(TRequest request) 
    { 
     TResult response; 

     using (DatabaseScope scope = DatabaseScopeManager.Instance.GetScope(
      this.connectionProvider, 
      DatabaseScopeType.UseExistingOrNewTX, 
      IsolationLevel.ReadCommitted)) 
     { 
      response = this.decoratee.Handle(request); 
      scope.Complete(); 
     } 

     return response;   
    } 
} 

И ExceptionWrapperBusinessDecorator:

public class ExceptionWrapperBusinessDecorator<TRequest, TResult> 
    : IBusiness<TRequest, TResult> 
    where TResult : BaseResponse 
{ 
    private readonly IBusiness<TRequest, TResult> decoratee; 

    public ExceptionWrapperBusinessDecorator(
     IBusiness<TRequest, TResult> decoratee) 
    { 
     this.decoratee = decoratee; 
    } 

    public TResult Send(TRequest request) 
    { 
     TResult response = Activator.CreateInstance<TResult>(); 

     try 
     { 
      var response = this.decoratee.Handle(request); 
      response.Success = true; 
     } 
     catch (Exception ex) 
     { 
      response.Success = false; 
      response.Message = exc.Message; 
     } 

     return response 
    } 
} 

Это позволяет AccountCreateBusiness быть записана следующим образом:

public sealed class AccountCreateBusiness 
    : IBusiness<AccountCreateRequest, AccountCreateResponse> 
{ 
    private readonly IMessageProvider messageProvider; 

    public AccountCreateBusiness(IMessageProvider messageProvider) 
    { 
     this.messageProvider = messageProvider; 
    } 

    public AccountCreateResponse Handle(AccountCreateRequest request) 
    { 
     //handle request 
    } 
} 

Обертывание каждый обработчик с декораторами тривиальна с большинством контейнеров и n делать это вручную (a.k.a. Pure DI).

Был последним советом по дизайну. Занимаясь каждый исключение и возвращать информацию как часть сообщения, как правило, плохая идея, потому что:

  • Эти коды ошибок просачивается в базе системы (ваш бизнес слой), который не имеет ничего общего с ним. Вместо того, чтобы эта информация была доступна в базовом ответе, используйте конверт, содержащий эту информацию.
  • Вы предоставите клиенту техническую информацию. Обычно клиент не интересуется этим уровнем детализации. Вы хотите только вернуть функциональные сообщения.
  • Информация, относящаяся к безопасности, возвращается; хакеры любят такую ​​информацию.
  • Если вы не регистрируете эту информацию, вы теряете множество важных сведений об ошибке.
  • Чтобы в принципе вернуть коды ошибок, вместо работы с исключениями. Коды ошибок очень подвержены ошибкам; клиент должен всегда проверять результат, чтобы увидеть, удалось ли ответить.
+1

Это гораздо лучший подход, спасибо. – dstr