소프트웨어 파이프 라인에서 공유 데이터를 캡슐화하기위한 우수한 구현 전략


13

기존 웹 서비스의 특정 측면을 리팩토링하려고 노력하고 있습니다. 서비스 API가 구현되는 방식은 일종의 "프로세싱 파이프 라인"을 갖는 것입니다. 여기서는 작업이 순서대로 수행됩니다. 당연히, 나중의 작업에는 이전 작업에 의해 계산 된 정보가 필요할 수 있으며 현재이 방법은 "파이프 라인 상태"클래스에 필드를 추가하는 것입니다.

나는 zillion 필드가있는 데이터 객체를 갖는 것보다 파이프 라인 단계간에 정보를 공유하는 더 좋은 방법이 있다고 생각했습니다 (그리고 희망합니까?). 일부는 처리 단계에 적합하지만 다른 처리 단계에는 의미가 없습니다. 이 클래스를 스레드로부터 안전하게 만드는 것은 큰 고통이 될 것입니다 (가능한지 모르겠습니다). 불변에 대해 추론 할 방법이 없습니다 (아마도 없을 것입니다).

영감을 찾기 위해 Gang of Four 디자인 패턴 서적을 통해 페이징했지만 솔루션이있는 것처럼 느껴지지 않았습니다 (Memento는 다소 같은 정신에 있었지만 그다지 좋지는 않았습니다). 또한 온라인을 보았지만 두 번째로 "파이프 라인"또는 "워크 플로우"를 검색하면 Unix 파이프 정보 또는 독점적 워크 플로우 엔진 및 프레임 워크가 넘칩니다.

내 질문은-소프트웨어 처리 파이프 라인의 실행 상태를 기록하는 문제에 어떻게 접근하여 나중에 작업이 이전 작업으로 계산 된 정보를 사용할 수 있습니까? 유닉스 파이프와의 주요 차이점은 바로 직전 작업의 출력에 신경 쓰지 않는다는 것입니다.


요청에 따라 내 유스 케이스를 설명하는 의사 코드가 있습니다.

"파이프 라인 컨텍스트"개체에는 여러 파이프 라인 단계가 채우거나 읽을 수있는 많은 필드가 있습니다.

public class PipelineCtx {
    ... // fields
    public Foo getFoo() { return this.foo; }
    public void setFoo(Foo aFoo) { this.foo = aFoo; }
    public Bar getBar() { return this.bar; }
    public void setBar(Bar aBar) { this.bar = aBar; }
    ... // more methods
}

각 파이프 라인 단계는 객체입니다.

public abstract class PipelineStep {
    public abstract PipelineCtx doWork(PipelineCtx ctx);
}

public class BarStep extends PipelineStep {
    @Override
    public PipelineCtx doWork(PipelieCtx ctx) {
        // do work based on the stuff in ctx
        Bar theBar = ...; // compute it
        ctx.setBar(theBar);

        return ctx;
    }
}

마찬가지로 가설의 FooStep경우 다른 데이터와 함께 이전에 BarStep에 의해 계산 된 Bar가 필요할 수 있습니다. 그리고 실제 API 호출이 있습니다.

public class BlahOperation extends ProprietaryWebServiceApiBase {
    public BlahResponse handle(BlahRequest request) {
        PipelineCtx ctx = PipelineCtx.from(request);

        // some steps happen here
        // ...

        BarStep barStep = new BarStep();
        barStep.doWork(crx);

        // some more steps maybe
        // ...

        FooStep fooStep = new FooStep();
        fooStep.doWork(ctx);

        // final steps ...

        return BlahResponse.from(ctx);
    }
}

6
포스트를 교차하지 말고 모드가 움직일 수 있도록 깃발을
ratchet freak

1
앞으로 나는 규칙에 익숙해지는 데 더 많은 시간을 할애해야한다고 생각합니다. 감사!
RuslanD December

1
구현을위한 영구적 인 데이터 스토리지를 피하고 있습니까?
CokoBWare

1
안녕하세요 RuslanD와 환영합니다! 이것은 스택 오버플로보다 프로그래머에게 더 적합하므로 SO 버전을 제거했습니다. @ratchetfreak이 언급 한 것을 명심하십시오. 중요 관심을 표시하고 교차 게시 할 필요없이 더 적합한 사이트로 마이그레이션 할 질문을 요청할 수 있습니다. 두 사이트 중 하나를 선택하는 데있어 일반적으로 프로그래머는 프로젝트를 설계하는 화이트 보드 앞에있을 때 직면하는 문제에 대한 것이고, 스택 오버 플로우는보다 기술적 인 문제 (예 : 구현 문제)에 대한 것입니다. 자세한 내용은 FAQ를 참조하십시오 .
yannis

1
파이프 라인 대신 아키텍처를 처리 DAG (지향 비순환 그래프)로 변경하면 이전 단계의 결과를 명시 적으로 전달할 수 있습니다.
Patrick

답변:


4

파이프 라인 디자인을 사용하는 주된 이유는 스테이지를 분리하려는 것입니다. 하나의 스테이지가 여러 파이프 라인 (예 : Unix 쉘 도구)에서 사용될 수 있거나 확장 이점이 있기 때문에 (즉, 단일 노드 아키텍처에서 다중 노드 아키텍처로 쉽게 이동할 수 있음).

두 경우 모두 파이프 라인의 각 단계에는 작업 수행에 필요한 모든 것이 제공되어야합니다. 외부 저장소 (예 : 데이터베이스)를 사용할 수없는 이유는 없지만 대부분의 경우 한 단계에서 다른 단계로 데이터를 전달하는 것이 좋습니다.

그러나 이것이 가능한 모든 필드와 함께 하나의 큰 메시지 객체를 전달해야하거나 전달해야한다는 의미는 아닙니다 (아래 참조). 대신 파이프 라인의 각 단계는 해당 단계에서 필요한 데이터 식별하는 입력 및 출력 메시지에 대한 인터페이스를 정의 해야합니다.

그런 다음 실제 메시지 객체를 구현하는 방법에 많은 유연성이 있습니다. 한 가지 방법은 필요한 모든 인터페이스를 구현하는 거대한 데이터 개체를 사용하는 것입니다. 또 다른 방법은 간단한 주위에 래퍼 클래스를 만드는 것 Map입니다. 또 다른 하나는 데이터베이스 주위에 랩퍼 클래스를 작성하는 것입니다.


1

떠오르는 생각이 몇 가지 있는데, 우선 충분한 정보가 없다고 생각합니다.

  • 각 단계는 파이프 라인을 넘어 사용 된 데이터를 생성합니까, 아니면 마지막 단계의 결과에만 관심이 있습니까?
  • 많은 빅 데이터 문제가 있습니까? 즉. 메모리 문제, 속도 문제 등

대답은 아마도 디자인에 대해 더 신중하게 생각할 것이지만, 아마도 내가 먼저 고려할 두 가지 접근법이 있다고 말한 것에 근거합니다.

각 단계를 자체 객체로 구성하십시오. n 번째 단계는 델리게이트 목록으로 1에서 n-1 개의 단계를 갖습니다. 각 단계는 데이터와 데이터 처리를 캡슐화합니다. 각 개체 내에서 전체적인 복잡성과 필드를 줄입니다. 델리게이트를 통과하여 훨씬 이전 단계에서 필요에 따라 이후 단계에서 데이터에 액세스하도록 할 수도 있습니다. 중요한 단계 (즉, 모든 속성)의 결과이기 때문에 여전히 모든 객체에 대해 매우 밀접한 결합이 있지만 크게 감소하고 각 단계 / 개체가 더 읽기 쉽고 이해하기 쉽습니다. 델리게이트 목록을 지연시키고 스레드 안전 대기열을 사용하여 필요에 따라 각 객체의 델리게이트 목록을 채우면 스레드를 안전하게 만들 수 있습니다.

또는 아마도 당신의 일과 비슷한 것을 할 것입니다. 각 단계를 나타내는 함수를 거치는 방대한 데이터 개체입니다. 이것은 종종 훨씬 더 빠르고 가벼우 나, 데이터 속성이 너무 많기 때문에 더 복잡하고 오류가 발생하기 쉽습니다. 분명히 스레드 안전하지 않습니다.

솔직히 나는 나중에 ETL 및 다른 유사한 문제에 대해 더 자주 수행했습니다. 유지 관리가 아닌 데이터 양 때문에 성능에 중점을 두었습니다. 또한 그들은 다시 사용되지 않는 일회성이었습니다.


1

이것은 GoF의 체인 패턴처럼 보입니다.

좋은 출발점은 커먼즈 체인 이 하는 일을 보는 것 입니다.

복잡한 처리 흐름의 실행을 구성하는 데 가장 많이 사용되는 기술은 고전적인 "Gang of Four"디자인 패턴 책에 설명 된 "책임의 체인"패턴입니다. 이 디자인 패턴을 구현하는 데 필요한 기본 API 계약은 매우 간단하지만 패턴 사용을 용이하게하고보다 다양한 여러 소스의 명령 구현 구성을 장려하는 기본 API를 갖는 것이 유용합니다.

이를 위해 Chain API는 계산을 "체인"으로 결합 할 수있는 일련의 "명령"으로 모델링합니다. 명령에 대한 API는 단일 메소드 ( execute()) 로 구성되며 , 계산의 동적 상태를 포함하는 "컨텍스트"매개 변수가 전달되며 리턴 값은 현재 체인에 대한 처리가 완료되었는지 여부를 판별하는 부울 값입니다 ( true) 또는 체인의 다음 명령에 처리를 위임해야하는지 여부 (false).

"컨텍스트"추상화는 실행되는 환경 (예 : 이러한 환경 중 하나의 API 계약에 직접 연결되지 않고 서블릿 또는 포틀릿에서 사용할 수있는 명령)에서 명령 구현을 분리하도록 설계되었습니다. 위임하기 전에 리소스를 할당 한 다음 반환 할 때 해제해야하는 명령의 경우 (위임 대상 명령에서 예외가 발생하더라도) "명령"의 "필터"확장 postprocess()은이 정리 방법을 제공합니다 . 마지막으로, 명령을 "카탈로그"로 저장하고 조회하여 실제로 어떤 명령 (또는 체인)이 실행되는지에 대한 결정을 연기 할 수 있습니다.

책임 체인 패턴 API의 유용성을 최대화하기 위해 기본 인터페이스 계약은 적절한 JDK 이외의 종속성이없는 방식으로 정의됩니다. 웹 환경 (서블릿 및 포틀릿)에 대한보다 전문화 된 (선택적) 구현뿐만 아니라 이러한 API의 편의 기본 클래스 구현이 제공됩니다.

명령 구현이 이러한 권장 사항을 준수하도록 설계되었으므로 웹 응용 프로그램 프레임 워크 (예 : Struts)의 "프론트 컨트롤러"에서 Chain of Responsibility API를 사용하는 것이 가능해야하지만 비즈니스에서도 사용할 수 있어야합니다. 논리 및 지속성 계층은 구성을 통해 복잡한 계산 요구 사항을 모델링합니다. 또한, 범용 컨텍스트에서 작동하는 개별 명령으로 계산을 분리하면 단위로 테스트 할 수있는 명령을보다 쉽게 ​​생성 할 수 있습니다. 명령 실행의 영향은 제공된 컨텍스트에서 해당 상태 변경을 관찰하여 직접 측정 할 수 있기 때문입니다. ...


0

내가 상상할 수있는 첫 번째 해결책은 단계를 명시 적으로 만드는 것입니다. 각각은 데이터를 처리 할 수있는 객체가되어 다음 프로세스 객체로 전송할 수 있습니다. 각 프로세스는 새로운 (이상적으로 불변 인) 제품을 생성하므로 프로세스간에 상호 작용이없고 데이터 공유로 인한 위험이 없습니다. 일부 프로세스가 다른 프로세스보다 시간이 오래 걸리는 경우 두 프로세스 사이에 버퍼를 배치 할 수 있습니다. 멀티 스레딩을위한 스케줄러를 올바르게 활용하면 버퍼를 플러시하기 위해 더 많은 리소스를 할당합니다.

두 번째 해결책은 전용 프레임 워크를 사용하여 파이프 라인 대신 "메시지"를 생각하는 것입니다. 그러면 다른 배우로부터 메시지를 받고 다른 배우에게 다른 메시지를 보내는 일부 "배우"가 있습니다. 파이프 라인에서 액터를 구성하고 체인을 시작한 첫 번째 액터에게 기본 데이터를 제공합니다. 공유가 메시지 전송으로 대체되므로 데이터 공유가 없습니다. 스칼라의 액터 모델은 여기에 스칼라가 없기 때문에 Java에서 사용할 수 있다는 것을 알고 있지만 Java 프로그램에서는 사용하지 않았습니다.

솔루션은 유사하며 첫 번째 솔루션으로 두 번째 솔루션을 구현할 수 있습니다. 기본적으로 주요 개념은 데이터 공유로 인한 기존의 문제를 피하고 파이프 라인의 프로세스를 나타내는 명시적이고 독립적 인 엔티티를 작성하기 위해 변경 불가능한 데이터를 처리하는 것입니다. 이러한 조건을 만족하면 명확하고 간단한 파이프 라인을 쉽게 만들어 병렬 프로그램에서 사용할 수 있습니다.


이봐, 나는 의사 코드로 내 질문을 업데이트했다. 우리는 실제로 단계가 명시 적이다.
RuslanD December
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.