New Score :0
High Score :0
Run Best
NICE BUSINESS TYPE INDICATOR
3. 급전을 친구에게 빌렸는데 오늘이 돈을 주기로 한날.. 그런데 카드값을 내야하는 날도 오늘인데... 이걸 어쩌나...
4. 우리 회사는 중요한 의사 결정을 할때?
5. 열심히 일한 나를 위한 선물을 주고싶다. 어떤게 좋을까?
6. 은행에서 투자상품을 추천받았다. 어떤걸 가입하지?
7. 회사에서의 나는?
8. 꿈에서 깨어나니 20년 전으로 돌아갔다. 당신이 제일 먼저 하는일은?
9. 내가 인사 담당자라면 신규 입사자 채용 시 제일 중요하게 보는것은?
10. 회사에 정말 싫어하는 동료가 있다면?
11. 가난한 집의 가장이 되었다.. 자녀의 생일 날 선물은?
12. 평소 회사 출근 스타일은?
13.회사 체육대회 하는 날이다. 오늘 뭐하지?
14. 나의 업무 스타일은?
Given problem
CommandBus 패턴의 문제는 CQRS 아키텍처 패턴의 자식 문제입니다. 아래의 유일한 질문은 항상 우리 머리 속에 존재합니다 : 우리 시스템에서 명령과 쿼리를 분리하는 방법은 무엇입니까?
우리 시스템이 더 복잡 해지면 데이터베이스는 일반적으로 해결해야 할 봇틀넥이기 때문입니다. 잠금 메커니즘은 동시성 액세스의 일부 문제를 방지하는 데 도움이되기 때문입니다. 그러나 그것은 또한 성능에 관한 또 다른 문제를 만듭니다.
그런 다음 명령 및 쿼리 개념을 CQRS 아키텍처 패턴의 두 가지로 분리하여 성능을 크게 향상시킵니다.
그렇다면 명령 버스 패턴을 어떻게 구현합니까?
Solution with Command Bus pattern
다음은 명령 버스 패턴에 대한 다이어그램입니다.
이 패턴에는 세 가지 클래스가 사용됩니다.
Command class
명령 클래스는 작업을 실행하는 데 필요한 데이터가 포함 된 클래스 일뿐입니다. 데이터 전송 개체 패턴과 같습니다.
명령과 명령 처리기 간의 관계는 일대일입니다. 즉, 하나의 명령은 하나의 CommandHandler에 의해서만 처리됩니다.
Command 클래스에서 데이터에 대한 간단한 유효성 검사를 수행 할 수 있으며 Command 객체는 변경할 수없는 객체입니다.
CommandHandler class
CommandHandler의 책임은 특정 Command 객체를 입력으로 사용할 때 적절한 도메인 동작을 실행하는 것입니다. CommandHandler는 도메인 논리 자체를 수행해서는 안됩니다. 이 이상의 작업을 수행해야 하는 경우 해당 논리를 래핑하는 서비스를 정의해야 합니다.
도메인 동작은 AggregateRoot에서 실행되어야 합니다. 따라서 CommandHandler 클래스는 저장소를 사용하여 AggregateRoot를로드하고 해당 데이터를 AggregateRoot에 전달합니다.
CommandBus class
Command 개체를 받은 후 CommandBus는 적절한 CommandHandler로 라우팅합니다.
CommandBus 클래스의 기능을 확장하려면 데코레이터 패턴 또는 프록시 패턴을 함께 사용할 수 있습니다. 데코레이터 패턴의 경우 CommandBus의 확장에 대한 몇 가지 샘플이 있습니다.
- 데이터베이스 트랜잭션에서 CommandBus를 래핑합니다.
- 로깅으로 CommandBus를 래핑하여 CommandHandler의 시간을 측정합니다.
프록시 패턴을 사용하면 명령을 처리하기 전에 권한을 확인하기 위해 CommandBus를 래핑합니다.
Source code
일반적으로 CommandBus에서 HashMap을 사용하여 Command 와 CommandHandler 간의 연결을 처리 할 수 있습니다. 그러나 우리의 경우 Guice의 DI 원칙 또는 IoC 컨테이너를 활용하여 Command 및 CommandHandler를 매핑합니다.
다음은 명령 버스 패턴에서 구현해야하는 단계입니다.
일반 인터페이스 ICommand와 해당 구현을 만듭니다.
public interface ICommand<R> {
// nothing to do
}
@Getter
@AllArgsConstructor
@NoArgsConstructor
public class WithdrawMoneyCommand implements ICommand<Void> {
private String username;
private String amount;
private String account;
}
일반 인터페이스 ICommandHandler 및 해당 구현을 만듭니다.
public interface ICommandHandler<C, R> {
R handle(C command);
}
public class WithdrawCommandHandler implements ICommandHandler<WithdrawCommand, Void> {
@Override
public Void handle(WithdrawCommand command) {
System.out.println(WithdrawCommandHandler.class.getName() + " handled.");
return null;
}
}
Guice에서 구체적인 모듈을 정의하여 Command 및 CommandHandler 클래스를 결합 하십시오.
ICommandHandler 인터페이스를 구현하는 명령 처리기가 여러 개 있기 때문에 Multibinder 를 사용하여 동일한 ICommandHandler 인터페이스를 구현하는 클래스를 수락합니다.
public class CommandHandlerModule extends AbstractModule {
@Override
protected void configure() {
Multibinder<ICommandHandler> commandHandlerBinder = Multibinder.newSetBinder(binder(), ICommandHandler.class);
commandHandlerBinder.addBinding().to(RegisterCommandHandler.class);
// define the other derived class of ICommandHandler interface
}
}
public class MainModule extends AbstractModule {
@Override
protected void configure() {
this.install(new CommandHandlerModule());
this.bind(ICommandBus.class).to(CommandBusImpl.class);
}
}
ICommandBus 인터페이스 및 구현 만들기.
public class CommandBusImpl implements ICommandBus {
private Set<ICommandHandler> commandHandlers;
@Inject
public CommandBusImpl(Set<ICommandHandler> commandHandlers) {
this.commandHandlers = commandHandlers;
}
@Override
public <C> void execute(C command) {
return (ICommandHandler<C, Void>) findCommandHandler(command);
}
@Override
public <C extends ICommand<R>, R> R execute(C command) {
return (ICommandHandler<C, R>) findCommandHandler(command);
}
private <C> ICommandHandler<C, ?> findCommandHandler(C command) {
Class<?> commandClazz = command.getClass();
return this.commandHandlers.stream()
.filter(handler -> this.canHandleCommand(handler.getClass(), commandClazz))
.findFirst()
.orElseThrow(() -> new RuntimeException("Do not handle " + commandClazz.getName()));
}
/**
* check the parameters's type that is corresponding to the type of Command class
*
*/
private boolean canHandleCommand(Class<?> handlerClazz, Class<?> commandClazz) {
Type[] genericInterfaces = handlerClazz.getGenericInterfaces();
ParameterizedType handlerIntefaceType = null;
for (Type type : genericInterfaces) {
if (type instanceof ParameterizedType) {
handlerIntefaceType = (ParameterizedType) type;
break;
}
}
Class<?> acceptableParameterClass = (Class<?>) handlerIntefaceType.getActualTypeArguments()[0];
return acceptableParameterClass.equals(commandClazz);
}
}
CommandBusImpl 클래스의 정의 :
public interface ICommandBus {
<C> void execute(C command);
<C extends ICommand<R>, R> R execute(C command);
}
그래서, 우리는 가지고 있습니다 :
import com.google.inject.Guice;
import com.google.inject.Injector;
import com.manhpd.bus.ICommandBus;
import com.manhpd.command.WithdrawCommand;
public class Application {
public static void main(String[] args) {
Injector injectorHandler = Guice.createInjector(new MainModule());
ICommandBus commandBus = injectorHandler.getInstance(ICommandBus.class);
WithdrawCommand command = new WithdrawCommand("John", "100000", "john");
commandBus.execute(command);
}
}
Benefits and Drawbacks
- 혜택
- 이 패턴은 도메인 기반 디자인 패턴에 적합합니다. 클라이언트가 명령을 정의하여 수행할 수 있는 작업에만 초점을 맞춥니다. 따라서 우리의 비즈니스 논리는 다른 계층에서 누출되지 않습니다.
사용자와 개발자 간의 의사 소통은 정말 원활합니다. 왜냐하면 그들은 다른 사람들이 말하는 것을 쉽게 이해할 수 있기 때문입니다. - 다른 패턴을 활용하면 CommandBus의 동작을 확장 할 수 있습니다.
- CommandBus 클래스를 사용하면 비동기 방식으로 Command 객체를 처리 할 수 있습니다.
비동기 방식을 사용하면 다음을 향상시킬 수 있습니다.- 멀티 스레드를 사용하여 처리 할 수 있기 때문에 시스템의 성능.
- UX는 사용자에 대한 응답이 빠르기 때문입니다.
- 이 패턴은 도메인 기반 디자인 패턴에 적합합니다. 클라이언트가 명령을 정의하여 수행할 수 있는 작업에만 초점을 맞춥니다. 따라서 우리의 비즈니스 논리는 다른 계층에서 누출되지 않습니다.
- 단점
- CommandHandler 코딩에 대해주의 깊게 생각하지 않으면 SRP를 준수하지 않는 CommandHandlers를 만들 수 있습니다.
- 일반적으로 리플렉션을 사용하여 Command 와 CommandHandler 사이를 매핑합니다. 따라서 런타임에 시스템의 성능에도 영향을 줄 수 있습니다.
Some problems with Command Bus pattern
- 때로는 CommandHandler 클래스에서 여러 작업을 정의하지만 기본 작업에 초점을 맞추지 않는 경우가 있습니다.
예를 들어, 우리가 웹 사이트에 로그인했다고 가정합니다. 이 명령의 기본 동작은 사용자 이름-암호를 포함하는 정보의 유효성을 검사하고 이 현재 사용자의 이 세션을 저장하는 것입니다. 또한 현재 사용자에 대해 이메일을 사용하여 현재 계정을 사용하는 사람이 있다는 것을 알 필요가 있습니다.
따라서 두 번째 작업은 현재 사용자에게 전자 메일을 보내는 것입니다.
CommandHandler에서 여러 보조 작업을 구현하는 경우. 그것은 우리의 CommandHandler 클래스를 오염시키고 단일 책임 원칙을 충족시키지 못합니다.
이 경우에 대한 우리의 해결책은 각 보조 작업에 대한 글로벌 이벤트를 정의하는 것입니다. 이벤트 버스 패턴을 사용한다는 것을 의미합니다.
또한 글로벌 이벤트와 도메인 이벤트의 차이점을 인식해야합니다.- 외부 행동으로 우리는 글로벌 이벤트를 사용해야합니다.
- 도메인 동작으로 도메인 이벤트를 발생시켜야합니다.
The relationship with other patterns
- 명령 패턴 및 명령 버스 패턴
- 명령 패턴은 명령 버스 패턴과 관련이 없습니다.
- 명령 패턴은 호출자와 수신자 간의 디커플링되는 문제를 해결하고, Command 객체는 메서드를 정의하여 작업을 수행하는 실제 객체를 숨깁니다.
- 명령 버스 패턴은 사용 사례의 선언과 해당 구현을 분리하는 데 사용되며 Command 객체는 작업을 실행하기 위한 데이터를 포함하는 메시지일 뿐입니다.
그리고 CommandHandler는 비즈니스 로직을 구현하는 클래스입니다.
따라서 실제로 Command 객체는 CommandHandler에 전달 된 메시지 일뿐입니다.
- 명령 버스 패턴 및 메시지 버스 패턴
- 명령 버스 패턴은 메시지 버스 패턴의 특정 패턴입니다. 메시지 또는 명령 객체를 사용하면 항상 처리기 또는 대상을 알고 있기 때문입니다.
Wrapping up
- 요즘에는 CommandBus 패턴을 구현하기 위해 일반적으로 리플렉션과 주석을 사용하여 DI 패턴을 사용하여 DI 패턴을 사용합니다.
- CommandBus 패턴의 또 다른 구현은 스프링 컨테이너, Guice, CDI와 같은 DI 프레임 워크를 사용하지 않고 데이터베이스의 모든 명령을 정의합니다. 따라서 런타임에 모든 것을로드 할 것입니다.
https://ducmanhphan.github.io/2020-12-02-command-bus-pattern/#given-problem
Refer:
https://github.com/cloudogu/command-bus
https://github.com/fdside/commandbus
https://www.sitepoint.com/command-buses-demystified-a-look-at-the-tactician-package/
https://matthiasnoback.nl/2015/01/a-wave-of-command-buses/