Back to Home
Code Showcase
Production-quality code patterns from my 9 years of experience
Processor Pattern
Java 8 • Design Pattern
#Strategy#Factory#Payment
// Interface defining contract for all payment processors
public interface PaymentProcessor {
PaymentMethod getMethod();
boolean supports(PaymentRequest request);
Either<PaymentError, PaymentResult> process(PaymentRequest request);
default int getPriority() { return 0; }
}
// Abstract class providing template method
public abstract class AbstractPaymentProcessor implements PaymentProcessor {
@Override
public Either<PaymentError, PaymentResult> process(PaymentRequest request) {
return validateRequest(request)
.flatMap(this::buildProviderRequest)
.flatMap(this::callProvider)
.flatMap(this::parseResponse)
.peek(result -> logTransaction(request, result));
}
// Template methods - subclass implement
protected abstract Either<PaymentError, ProviderRequest> buildProviderRequest(PaymentRequest req);
protected abstract Either<PaymentError, ProviderResponse> callProvider(ProviderRequest req);
protected abstract Either<PaymentError, PaymentResult> parseResponse(ProviderResponse resp);
}
// Concrete implementation - MoMo
@Component
@RequiredArgsConstructor
public class MoMoProcessor extends AbstractPaymentProcessor {
private final MoMoClient momoClient;
private final SignatureService signatureService;
@Override
public PaymentMethod getMethod() {
return PaymentMethod.MOMO;
}
@Override
protected Either<PaymentError, ProviderRequest> buildProviderRequest(PaymentRequest req) {
var momoReq = MoMoRequest.builder()
.partnerCode(config.getPartnerCode())
.orderId(req.getOrderId())
.amount(req.getAmount())
.signature(signatureService.sign(req))
.build();
return Either.right(momoReq);
}
@Override
protected Either<PaymentError, ProviderResponse> callProvider(ProviderRequest req) {
try {
return Either.right(momoClient.createPayment((MoMoRequest) req));
} catch (Exception e) {
return Either.left(new ProviderConnectionError(e));
}
}
}
// Registry auto-collects all processors via DI
@Component
public class ProcessorRegistry {
private final List<PaymentProcessor> processors;
public ProcessorRegistry(List<PaymentProcessor> processors) {
this.processors = processors.stream()
.sorted(Comparator.comparing(PaymentProcessor::getPriority).reversed())
.collect(Collectors.toList());
}
public Either<PaymentError, PaymentResult> route(PaymentRequest request) {
return processors.stream()
.filter(p -> p.supports(request))
.findFirst()
.map(p -> p.process(request))
.orElse(Either.left(new NoProcessorFoundError(request.getMethod())));
}
}Key Points
- 1Interface + Abstract class: Define contract and template method
- 2Either monad: Functional error handling, no thrown exceptions
- 3Auto-discovery: Spring auto-injects all implementations
- 4Priority routing: Processors sorted by priority for selection
- 5Adding new provider: Just create new class implementing interface