development

Windsor-컨테이너에서 Transient 개체 가져 오기

big-blog 2020. 12. 13. 10:09
반응형

Windsor-컨테이너에서 Transient 개체 가져 오기


본질적으로 일시적인 컨테이너에서 객체를 어떻게 가져올 수 있습니까? 컨테이너에 등록하고 필요한 클래스의 생성자에 삽입해야합니까? 모든 것을 생성자에 주입하는 것은 기분이 좋지 않습니다. 또한 하나의 클래스에 대해서만 생성 TypedFactory하고 필요한 클래스에 팩토리를 주입하고 싶지 않습니다 .

나에게 떠오른 또 다른 생각은 필요에 따라 "새로운"것입니다. 그러나 나는 또한 Logger모든 클래스에 (속성을 통해) 구성 요소를 주입하고 있습니다. 따라서 새로 만들면 Logger해당 클래스에서 수동으로 인스턴스화해야합니다 . 모든 수업에 컨테이너를 계속 사용하려면 어떻게해야합니까?

로거 주입 :Logger 상속 체인이있는 경우를 제외하고대부분의 클래스에는속성이 정의되어 있습니다 (이 경우 기본 클래스에만이 속성이 있고 모든 파생 클래스가이를 사용함). 이것이 Windsor 컨테이너를 통해 인스턴스화되면 내 구현이ILogger주입됩니다.

//Install QueueMonitor as Singleton
Container.Register(Component.For<QueueMonitor>().LifestyleSingleton());
//Install DataProcessor as Trnsient
Container.Register(Component.For<DataProcessor>().LifestyleTransient());

Container.Register(Component.For<Data>().LifestyleScoped());

public class QueueMonitor
{
    private dataProcessor;

    public ILogger Logger { get; set; }

    public void OnDataReceived(Data data)
    {
        //pull the dataProcessor from factory    
        dataProcessor.ProcessData(data);
    }
}

public class DataProcessor
{
    public ILogger Logger { get; set; }

    public Record[] ProcessData(Data data)
    {
        //Data can have multiple Records
        //Loop through the data and create new set of Records
        //Is this the correct way to create new records?
        //How do I use container here and avoid "new" 
        Record record = new Record(/*using the data */);
        ...

        //return a list of Records    
    }
}


public class Record
{
    public ILogger Logger { get; set; }

    private _recordNumber;
    private _recordOwner;

    public string GetDescription()
    {
        Logger.LogDebug("log something");
        // return the custom description
    }
}

질문 :

  1. Record"새로 만들기"를 사용하지 않고 개체를 만들려면 어떻게합니까 ?

  2. QueueMonitorSingleton반면, Data"범위가 지정된"입니다. 어떻게 주입 할 수 있습니다 DataOnDataReceived()방법은?


제공하는 샘플에서 매우 구체적으로 말하기는 어렵지만 일반적 ILogger으로 대부분의 서비스에 인스턴스를 삽입 할 때 다음 두 가지를 자문해야합니다.

  1. 너무 많이 기록합니까?
  2. SOLID 원칙을 위반합니까?

1. 너무 많이 기록합니까

다음과 같은 코드가 많을 때 너무 많이 로깅하고 있습니다.

try
{
   // some operations here.
}
catch (Exception ex)
{
    this.logger.Log(ex);
    throw;
}

이와 같은 코드를 작성하는 것은 오류 정보를 잃을 염려에서 비롯됩니다. 그러나 이러한 종류의 try-catch 블록을 여기 저기에 복제하는 것은 도움이되지 않습니다. 더 나쁜 것은 종종 개발자가 기록하고 계속하는 것을 볼 수 있습니다 (마지막 throw을 제거함 ). 이것은 정말 나쁘다 (그리고 오래된 VB 냄새가 난다ON ERROR RESUME NEXT행동), 대부분의 상황에서 안전 여부를 판단하기에 충분한 정보가 없기 때문입니다. 종종 코드에 버그가 있거나 데이터베이스와 같은 외부 리소스에 딸꾹질이있어 작업이 실패했습니다. 계속한다는 것은 사용자가 작업이 성공했지만 성공하지 못했다는 생각을 자주 얻는다는 것을 의미합니다. 자신에게 물어보십시오. 더 나쁜 것이 무엇인지, 사용자에게 문제가 발생했다는 일반적인 오류 메시지를 표시하고 다시 시도해달라고 요청하거나 조용히 오류를 건너 뛰고 사용자가 요청이 성공적으로 처리 되었다고 생각 하게하는 입니까? 2 주 후에 주문한 상품이 배송되지 않았다는 사실을 알게되면 사용자가 어떻게 느낄지 생각해보세요. 아마도 고객을 잃을 것입니다. 또는 환자의 MRSA 등록이 조용히 실패하여 환자가 간호에 의해 격리되지 않고 다른 환자가 오염되어 높은 비용을 초래하거나 사망에이를 수도 있습니다.

이러한 종류의 try-catch-log 행 대부분은 제거해야하며 예외가 호출 스택에 버블 링되도록해야합니다.

기록하지 않겠습니까? 당신은 절대적으로해야합니다! 그러나 가능하다면 애플리케이션 상단에 하나의 try-catch 블록을 정의하십시오. ASP.NET을 사용하면 Application_Error이벤트를 구현 HttpModule하고 로깅을 수행하는 사용자 지정 오류 페이지를 등록 하거나 정의 할 수 있습니다 . WinForms를 사용하면 솔루션이 다르지만 개념은 동일하게 유지됩니다. 가장 포괄적 인 하나의 상위 항목을 정의합니다.

그러나 때로는 특정 유형의 예외를 포착하고 기록하고 싶을 때가 있습니다. 내가 과거에 작업 한 시스템은 비즈니스 계층이 ValidationExceptions를 던지도록했으며, 이는 프레젠테이션 계층에 의해 포착됩니다. 이러한 예외에는 사용자에게 표시 할 유효성 검사 정보가 포함되어 있습니다. 이러한 예외는 프리젠 테이션 계층에서 포착되고 처리되기 때문에 애플리케이션의 대부분의 상단까지 버블 링되지 않고 애플리케이션의 포괄 코드로 끝나지 않았습니다. 그래도 사용자가 잘못된 정보를 얼마나 자주 입력했는지 확인하고 올바른 이유로 유효성 검사가 트리거되었는지 확인하기 위해이 정보를 기록하고 싶었습니다. 따라서 이것은 오류 로깅이 아닙니다. 그냥 로깅. 이 작업을 수행하기 위해 다음 코드를 작성했습니다.

try
{
   // some operations here.
}
catch (ValidationException ex)
{
    this.logger.Log(ex);
    throw;
}

익숙해 보입니까? 예, ValidationException예외 만 포착했다는 점을 제외 하면 이전 코드 조각과 똑같습니다 . 그러나이 스 니펫 만 보면 알 수없는 또 다른 차이점이 있습니다. 해당 코드가 포함 된 응용 프로그램 에는 단 한 곳만 있었습니다 ! 데코레이터 였는데, 다음 질문을 스스로에게 물어 보았습니다.

2. SOLID 원칙을 위반합니까?

로깅, 감사 및 보안과 같은 것을 교차 문제 (또는 측면)라고합니다. 교차 절단 이라고 합니다. 애플리케이션의 여러 부분을 절단 할 수 있고 종종 시스템의 많은 클래스에 적용되어야하기 때문입니다. 그러나 시스템의 많은 클래스에서 사용할 코드를 작성하는 경우 SOLID 원칙을 위반할 가능성이 높습니다. 예를 들어 다음 예를 살펴보십시오.

public void MoveCustomer(int customerId, Address newAddress)
{
    var watch = Stopwatch.StartNew();

    // Real operation

    this.logger.Log("MoveCustomer executed in " +
        watch.ElapsedMiliseconds + " ms.");
}

여기서 MoveCustomer작업 을 실행하는 데 걸리는 시간을 측정하고 해당 정보를 기록합니다. 시스템의 다른 작업에는 이와 동일한 교차 절단 문제가 필요할 가능성이 큽니다. 당신은 당신을 위해 다음과 같은 코드를 추가 시작 ShipOrder, CancelOrder, CancelShipping, 등의 사용 사례, 결국 코드 중복의 많은 및 유지 보수 악몽이 리드 (내가 거기 있었어.)

이 코드의 문제점은 SOLID 원칙을 위반한다는 것 입니다. SOLID 원칙은 유연하고 유지 관리 할 수있는 (객체 지향) 소프트웨어를 정의하는 데 도움이되는 일련의 객체 지향 설계 원칙입니다. MoveCustomer예는 다음 규칙 중 두 개 이상을 위반했습니다.

  1. 단일 책임 원칙 -classes는 하나의 책임이 있어야합니다. MoveCustomer그러나 메서드를 포함하는 클래스 는 핵심 비즈니스 논리를 포함 할뿐만 아니라 작업을 수행하는 데 걸리는 시간도 측정합니다. 즉, 여러 책임이 있습니다.
  2. 오픈 폐쇄 원칙 (OCP)는 - 그것은 않아도 방지 당신이 코드베이스에 걸쳐 청소를 변경할 수 있음을 응용 프로그램 설계를 규정; 또는 OCP의 어휘에서 클래스는 확장을 위해 열려 있어야하지만 수정을 위해 닫혀 있어야합니다. 사용 사례에 예외 처리 (세 번째 책임)를 추가해야하는 MoveCustomer경우 (다시) MoveCustomer메서드 를 변경해야 합니다. 그러나 MoveCustomer방법 을 변경해야 할 뿐만 아니라 다른 많은 방법도 변경해야합니다. OCP는 DRY 원칙 과 밀접한 관련이 있습니다.

이 문제에 대한 해결책은 로깅을 자체 클래스로 추출하고 해당 클래스가 원래 클래스를 래핑하도록 허용하는 것입니다.

// The real thing
public class MoveCustomerService : IMoveCustomerService
{
    public virtual void MoveCustomer(int customerId, Address newAddress)
    {
        // Real operation
    }
}

// The decorator
public class MeasuringMoveCustomerDecorator : IMoveCustomerService
{
    private readonly IMoveCustomerService decorated;
    private readonly ILogger logger;

    public MeasuringMoveCustomerDecorator(
        IMoveCustomerService decorated, ILogger logger)
    {
        this.decorated = decorated;
        this.logger = logger;
    }

    public void MoveCustomer(int customerId, Address newAddress)
    {
        var watch = Stopwatch.StartNew();

        this.decorated.MoveCustomer(customerId, newAddress);

        this.logger.Log("MoveCustomer executed in " +
            watch.ElapsedMiliseconds + " ms.");
    }
}

실제 인스턴스 주위에 데코레이터를 래핑하면 이제 시스템의 다른 부분을 변경하지 않고도이 측정 동작을 클래스에 추가 할 수 있습니다.

IMoveCustomerService command =
    new MeasuringMoveCustomerDecorator(
        new MoveCustomerService(),
        new DatabaseLogger());

그러나 이전 예제는 문제의 일부만 해결했습니다 (SRP 부분 만). 위와 같이 코드를 작성하면 시스템의 모든 작업에 대해 별도의 장식을 정의해야합니다, 당신은 같은 장식하게 될 겁니다 MeasuringShipOrderDecorator, MeasuringCancelOrderDecorator그리고 MeasuringCancelShippingDecorator. 이로 인해 많은 중복 코드 (OCP 원칙 위반)가 발생하고 시스템의 모든 작업에 대해 코드를 작성해야합니다. 여기서 누락 된 것은 시스템의 사용 사례에 대한 일반적인 추상화입니다.

빠진 것은 ICommandHandler<TCommand>인터페이스입니다.

이 인터페이스를 정의 해 보겠습니다.

public interface ICommandHandler<TCommand>
{
    void Execute(TCommand command);
}

And let's store the method arguments of the MoveCustomer method into its own (Parameter Object) class called MoveCustomerCommand:

public class MoveCustomerCommand
{
    public int CustomerId { get; set; }
    public Address NewAddress { get; set; }
}

And let's put the behavior of the MoveCustomer method in a class that implements ICommandHandler<MoveCustomerCommand>:

public class MoveCustomerCommandHandler : ICommandHandler<MoveCustomerCommand>
{
    public void Execute(MoveCustomerCommand command)
    {
        int customerId = command.CustomerId;
        Address newAddress = command.NewAddress;
        // Real operation
    }
}

This might look weird at first, but because you now have a general abstraction for use cases, you can rewrite your decorator to the following:

public class MeasuringCommandHandlerDecorator<TCommand>
    : ICommandHandler<TCommand>
{
    private ILogger logger;
    private ICommandHandler<TCommand> decorated;

    public MeasuringCommandHandlerDecorator(
        ILogger logger,
        ICommandHandler<TCommand> decorated)
    {
        this.decorated = decorated;
        this.logger = logger;
    }

    public void Execute(TCommand command)
    {
        var watch = Stopwatch.StartNew();

        this.decorated.Execute(command);

        this.logger.Log(typeof(TCommand).Name + " executed in " +
            watch.ElapsedMiliseconds + " ms.");
    }
}

This new MeasuringCommandHandlerDecorator<T> looks much like the MeasuringMoveCustomerDecorator, but this class can be reused for all command handlers in the system:

ICommandHandler<MoveCustomerCommand> handler1 =
    new MeasuringCommandHandlerDecorator<MoveCustomerCommand>(
        new MoveCustomerCommandHandler(),
        new DatabaseLogger());

ICommandHandler<ShipOrderCommand> handler2 =
    new MeasuringCommandHandlerDecorator<ShipOrderCommand>(
        new ShipOrderCommandHandler(),
        new DatabaseLogger());

This way it will be much, much easier to add cross-cutting concerns to your system. It's quite easy to create a convenient method in your Composition Root that can wrap any created command handler with the applicable command handlers in the system. For instance:

private static ICommandHandler<T> Decorate<T>(ICommandHandler<T> decoratee)
{
    return
        new MeasuringCommandHandlerDecorator<T>(
            new DatabaseLogger(),
            new ValidationCommandHandlerDecorator<T>(
                new ValidationProvider(),
                new AuthorizationCommandHandlerDecorator<T>(
                    new AuthorizationChecker(
                        new AspNetUserProvider()),
                    new TransactionCommandHandlerDecorator<T>(
                        decoratee))));
}

This method can be used as follows:

ICommandHandler<MoveCustomerCommand> handler1 = 
    Decorate(new MoveCustomerCommandHandler());

ICommandHandler<ShipOrderCommand> handler2 =
    Decorate(new ShipOrderCommandHandler());

If your application starts to grow, however, it can get useful to bootstrap this with a DI Container, because a DI Container can use Auto-Registration. This prevents you from having to make changes to your Composition Root for every new command/handler pair you add to the system. Especially when your decorators have generic type constraints, a DI Container be extremely useful.

Most modern DI Containers for .NET have fairly decent support for decorators nowadays, and especially Autofac (example) and Simple Injector (example) make it easy to register open-generic decorators. Simple Injector even allows decorators to be applied conditionally based on a given predicate or complex generic type constraints, allowing the decorated class to be injected as a factory and allowing a contextual context to be injected into decorators, all of which can be really useful from time to time.

Unity and Castle, on the other hand, have dynamic interception facilities (as Autofac does to btw). Dynamic interception has a lot in common with decoration, but it uses dynamic proxy generation under the covers. This can be more flexible than working with generic decorators, but you pay the price when it comes to maintainability, because you often loose type safety and interceptors always force you to take a dependency on the interception library, while decorators are type safe and can be written without taking a dependency on an external library.

Read this article if you want to learn more about this way of designing your application: Meanwhile... on the command side of my architecture.

UPDATE: I also coauthored a book called Dependency Injection Principles, Practices, and Patterns, which goes into much more detail on this SOLID programming style and the design described above (see chapter 10).

참고URL : https://stackoverflow.com/questions/9892137/windsor-pulling-transient-objects-from-the-container

반응형