Pipeline design pattern implementation using Spring Boot - spring-boot

Can someone guide me as how to implement the Pipeline design pattern using Spring Boot?
I understand that each Pipe step has to be implemented as a #Service. I would like to know if #Ordered can be added to have the steps executed without explicitly wiring the individual #Service classes as the number of pipes in my case could exceed 10.

You can have something like that:
public interface Pipe{
void chain(Message input, Pipeline pipeline);
}
public class abstract AbstractPipe implements Pipe {
public void chain(Message input, Pipeline pipeline){
try {
//Business process of pipe1 (i.e. process the message and return response (Result of processing))
Response response = processMessage(input);
//Sucessful while processing
Message output = onSuccess(response, input);
pipeline.next(output);
}catch(Exception exception){
//Failure while processing
onError(exception, input);
//Maybe, we can continue
//with an alternative message
}
}
public abstract Message onSuccess(Response response, Message originalMessage);
public abstract void onError(Throwable error, Message input);
public abstract Response processMessage(Message input);
}
#Service
public class Pipe1 extends AbstractPipe {
// Implementation of abstract methods
}
#Service
public class Pipe2 extends AbstractPipe {
// Implementation of abstract methods
}
#Service
public class Pipe3 extends AbstractPipe {
// Implementation of abstract methods
}
public interface Message {
public Object get(String key);
}
public class MessageImpl implements Message {
private final Map<String, Object> index;
public MessageImpl(){...}
public void set(String key, Objetc value){...}
public Object get(String key){...}
}
The Pipeline is a chain of Pipes. Each pipe executes a command and tell the pipeline to continue with a new message.
I'm not sure that Spring has an annotation called #Ordered. Maybe you mean #Order which :
defines the sort order for an annotated component.
I'm not sure that it can add something to the Pipeline implementation.
Hope that helps.

Related

Spring cloud stream : how to use #Transactional with new Consumer<> functional programming model

I have StreamListener which I would like to replace using the new functional model and Consumer <>. Unfortunately, I don't know how to transfer #Transactional to new model:
#Transactional
#StreamListener(PaymentChannels.PENDING_PAYMENTS_INPUT)
public void executePayments(PendingPaymentEvent event) throws Exception {
paymentsService.triggerInvoicePayment(event.getInvoiceId());
}
I have tired certain things. Sample code below. I added logging messages to a different queue for tests. Then I throw an exception to trigger a rollback. Unfortunately, messages are queued even though they are not there until the method is completed (I tested this using brakepoints). It seems that the transaction was automatically committed despite the error.
#Transactional
#RequiredArgsConstructor
#Component
public class functionalPayment implements Consumer<PendingPaymentEvent> {
private final PaymentsService paymentsService;
private final StreamBridge streamBridge;
public void accept(PendingPaymentEvent event) {
paymentsService.triggerInvoicePayment(event.getInvoiceId());
streamBridge.send("log-out-0",event);
throw new RuntimeException("Test exception to rollback message from log-out-0");
}
}
Configuration:
spring.cloud.stream.rabbit.bindings.functionalPayment-in-0.consumer.queue-name-group-only=true
spring.cloud.stream.rabbit.bindings.functionalPayment-in-0.consumer.declare-exchange=true
spring.cloud.stream.rabbit.bindings.functionalPayment-in-0.consumer.bind-queue=true
spring.cloud.stream.rabbit.bindings.functionalPayment-in-0.consumer.transacted=true
spring.cloud.stream.source=log
spring.cloud.stream.bindings.log-out-0.content-type=application/json
spring.cloud.stream.bindings.log-out-0.destination=log_a
spring.cloud.stream.bindings.log-out-0.group=log_a
spring.cloud.stream.rabbit.bindings.log-out-0.producer.declare-exchange=true
spring.cloud.stream.rabbit.bindings.log-out-0.producer.bind-queue=true
spring.cloud.stream.rabbit.bindings.log-out-0.producer.queue-name-group-only=true
spring.cloud.stream.rabbit.bindings.log-out-0.producer.binding-routing-key=log
spring.cloud.stream.rabbit.bindings.log-out-0.producer.transacted=true
spring.cloud.stream.rabbit.bindings.log-out-0.producer.exchange-type=direct
spring.cloud.stream.rabbit.bindings.log-out-0.producer.routing-key-expression='log'
Have you tried something along the lines of
#Transactional
public class ExecutePaymentConsumer implements Consumer<PendingPaymentEvent> {
public void accept(PendingPaymentEvent event) {
paymentsService.triggerInvoicePayment(event.getInvoiceId());
}
}
. . .
#Bean
public ExecutePaymentConsumer executePayments() {
return new ExecutePaymentConsumer();
}

How can I create a Pointcut or Around for extended classes with shared interface?

I have an abstract service class.
abstract class AbstractService<T> {
public void saveNew(T entity) {
}
}
And I have two more abstract classes extends AbstractService and implement a shared interface.
abstract class MoreAbstractService2<T extends Some2>
extends AbstractService<T>
implements SharedInterface {
}
abstract class MoreAbstractService3<T extends Some3>
extends AbstractService<T>
implements SharedInterface {
}
Now I want to validate the entity argument on these two extending services' saveNew(T) method.
How can I define a #Pointcut and (or) an #Around for following conditions?
extends the AbstractService class
implements the SharedInterface interface
you can use within as following:
within(com.somepackage.Super+)
where com.somepackage.Super is the fully qualified base class name and + means "all subclasses". Other pointcut is
execution(* com.somepackage.Super+.*(..))
R.G's solution has a few disadvantages:
The pointcut matches too many joinpoints.
Thus it needs reflection in order to filter out the unnecessary ones.
I am going to show you a stand-alone AspectJ solution (no Spring, I am not a Spring user), but the aspect would look just the same in Spring, you only need to make it a #Component or declare a #Bean factory in your configuration. But the same applies to all the classes you want to intercept, of course.
Because I prefer a full MCVE with all necessary dependency classes in order for you to be able to copy, compile and run it, and because I also added negative test cases (sub-classes only extending the abstract base class or only implementing the interface), this is a lot of code. So please bear with me:
Abstract classes, interface and helper classes:
package de.scrum_master.app;
public abstract class AbstractService<T> {
public void saveNew(T entity) {
System.out.println("Saving new entity " + entity);
}
}
package de.scrum_master.app;
public class Some2 {}
package de.scrum_master.app;
public class Some3 {}
package de.scrum_master.app;
public abstract class MoreAbstractService2<T extends Some2>
extends AbstractService<T>
implements SharedInterface {}
package de.scrum_master.app;
public abstract class MoreAbstractService3<T extends Some3>
extends AbstractService<T>
implements SharedInterface {}
package de.scrum_master.app;
public interface SharedInterface {
void doSomething();
}
Driver application (AspectJ + POJOs, not Spring):
This driver class contains some static inner classes subclassing the given base classes and/or implementing the shared interface. Two are used for positive tests (should be intercepted), two for negative tests (should not be intercepted). Each class also contains an additional method as another negative test case which should not be matched - better safe than sorry.
package de.scrum_master.app;
public class Application {
public static void main(String[] args) {
// Should be intercepted
InterceptMe1 interceptMe1 = new InterceptMe1();
interceptMe1.saveNew(new Some2());
interceptMe1.doSomething();
interceptMe1.additional();
printSeparator();
// Should be intercepted
InterceptMe2 interceptMe2 = new InterceptMe2();
interceptMe2.saveNew(new Some3());
interceptMe2.doSomething();
interceptMe2.additional();
printSeparator();
// Should NOT be intercepted
DontInterceptMe1 dontInterceptMe1 = new DontInterceptMe1();
dontInterceptMe1.saveNew(new Some2());
dontInterceptMe1.additional();
printSeparator();
// Should NOT be intercepted
DontInterceptMe2 dontInterceptMe2 = new DontInterceptMe2();
dontInterceptMe2.additional();
printSeparator();
}
private static void printSeparator() {
System.out.println("\n----------------------------------------\n");
}
static class InterceptMe1 extends MoreAbstractService2<Some2> {
#Override
public void doSomething() {
System.out.println("Doing something in MoreAbstractService2<Some2>");
}
public void additional() {
System.out.println("Additional method in MoreAbstractService2<Some2>");
}
}
static class InterceptMe2 extends MoreAbstractService3<Some3> {
#Override
public void doSomething() {
System.out.println("Doing something in MoreAbstractService3<Some3>");
}
public void additional() {
System.out.println("Additional method in MoreAbstractService3<Some3>");
}
}
static class DontInterceptMe1 extends AbstractService<Some2> {
public void additional() {
System.out.println("Additional method in AbstractService<Some2>");
}
}
static class DontInterceptMe2 implements SharedInterface {
#Override
public void doSomething() {
System.out.println("Doing something in SharedInterface"); }
public void additional() {
System.out.println("Additional method in SharedInterface");
}
}
}
Aspect:
package de.scrum_master.aspect;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
#Aspect
public class EntityValidationAspect {
#Before(
"execution(* saveNew(*)) && " +
"args(entity) && " +
"target(de.scrum_master.app.SharedInterface) && " +
"target(de.scrum_master.app.AbstractService)"
)
public void validateEntity(JoinPoint thisJoinPoint, Object entity) {
System.out.println("-> Pre-save entity validation: " + entity);
}
}
As you can see, the aspect uses two target() pointcuts which must both match. It also specifically targets any saveNew method with a single argument saveNew(*), binding that argument as an advice method parameter via args().
For demonstration's sake I do not validate anything (I don't know how you want to do that) but just print the entity. Thus, a #Before advice is sufficient. If in case of negative validation you want to throw an exception, this advice type is also okay. If you need to do more, such as manipulate the entity's state or replace it before passing it on to the target method, call an alternative target method instead or none at all, return a specific result (in case of non-void methods, here not applicable), handle exceptions from the target method etc., you ought to use an #Around advice instead.
Console log:
-> Pre-save entity validation: de.scrum_master.app.Some2#28a418fc
Saving new entity de.scrum_master.app.Some2#28a418fc
Doing something in MoreAbstractService2<Some2>
Additional method in MoreAbstractService2<Some2>
----------------------------------------
-> Pre-save entity validation: de.scrum_master.app.Some3#5305068a
Saving new entity de.scrum_master.app.Some3#5305068a
Doing something in MoreAbstractService3<Some3>
Additional method in MoreAbstractService3<Some3>
----------------------------------------
Saving new entity de.scrum_master.app.Some2#1f32e575
Additional method in AbstractService<Some2>
----------------------------------------
Additional method in SharedInterface
----------------------------------------
Et voilà - the aspect does exactly what you asked for, as far as I understand your requirement. :-) More specifically, it does not get triggered in the third case when saveNew is being called, but the class does not implement the interface.
Following code can be used for the validation mentioned.
The point cut is to intercept on the execution of a specific method for the subclasses of AbstractService and the code logic is to only validate if SharedInterface is a superinterface of the target bean.
The use of isAssignableFrom() is required as the interfaces proxied by the AOP proxy does not include SharedInterface. As per my understanding , a pointcut expression to match the second criteria will not be possible for the same reason and hence handled the requirement in the code logic.
Hope this helps
#Aspect
#Component
public class ValidationAspect {
#Pointcut("execution(* package.to.AbstractService+.saveNew(..))")
public void isAbstractServiceType() {
}
#Around("isAbstractServiceType() && args(entity) && target(bean)")
public void validateEntityArugments(ProceedingJoinPoint pjp, Object entity, Object bean) throws Throwable {
if (SharedInterface.class.isAssignableFrom(bean.getClass())) {
System.out.println(entity);
// ..validate
}
pjp.proceed();
}
}

Spring Microservices issue with #HystrixCommand

We are facing a problem with Hystrix Command in a Spring Boot / Cloud microservice. We have a Spring Component containing a method annotated with #RabbitListener. When a new message arrives, the method delegates the invocation to NotificationService::processNotification().
The NotificationService is a bean annotated with #Service. The method processNotification() can request third party applications. We want to wrap the invocation of third party applications using #HystrixCommand to provide fault tolerance, but due to some reasons the Hystrix Command annotated method is not working.
If we invoke a Controller and the Controller delegates the invocation to a Service method, which in turns have a Hystrix Command , everything works perfectly. The only problem with Hystrix Command arises when the microservices consume a messages and it seems to be Hystrix Command doesn’t trigger the fallback method.
Here is the non-working code:
#Component
public class MessageProcessor {
#Autowired
private NotificationService notificationService;
#RabbitListener(queues = "abc.xyz-queue")
public void onNewNotification(String payload) {
this.notificationService.processNotification(payload);
}
}
#Service
public class NotificationService {
public void processNotification(String payload) {
...
this.notifyThirdPartyApp(notificationDTO);
...
}
#HystrixCommand(fallbackMethod = "notifyThirdPartyAppFallback")
public void notifyThirdPartyApp(NotificationDTO notificationDTO) {
//Do stuff here that could fail
}
public void notifyThirdPartyAppFallback(NotificationDTO notificationDTO) {
// Fallbacl impl goes here
}
}
#SpringBootApplication
#EnableCaching
#EnableCircuitBreaker
#EnableDiscoveryClient
#EnableRabbit
public class NotificationApplication {
public static void main(String[] args) {
SpringApplication.run(NotificationApplication.class, args);
}
}
I'm not sure about your problem without looking at the code.
As another approach you can take: instead of describing this calls with annotations in your service, just extend HystrixCommand and implement api calling logic in it (read more):
public class CommandHelloWorld extends HystrixCommand<String> {
private final String name;
public CommandHelloWorld(String name) {
super(HystrixCommandGroupKey.Factory.asKey("ExampleGroup"));
this.name = name;
}
#Override
protected String run() {
// a real example would do work like a network call here
return "Hello " + name + "!";
}
}

Events in Spring Boot

Is it possible to use events in Spring Boot? I need to execute one method but without waiting for return. I'm trying to use this:
public class GerarSeloEvent extends ApplicationEvent {
private TbPedido pedido;
private Integer cdCartorio;
public GerarSeloEvent(Object source, TbPedido pedido, Integer cdCartorio) {
super(source);
this.pedido = pedido;
this.cdCartorio = cdCartorio;
}
public TbPedido getPedido() {
return pedido;
}
public Integer getCdCartorio() {
return cdCartorio;
}
}
#Component
public class GerarSeloListener implements ApplicationListener<GerarSeloEvent> {
#Autowired
SeloService seloService;
#Override
public void onApplicationEvent(GerarSeloEvent event) {
seloService.gerarSelos(event.getPedido(), event.getCdCartorio());
}
}
and my call
GerarSeloEvent gerarSelos = new GerarSeloEvent(this, pedido, cdCartorio);
EnviarEmailPedidoEvent enviarEmail = new EnviarEmailPedidoEvent(this, pedido);
publisher.publishEvent(gerarSelos);
But my code waits to return anything to my front-end. I need one async event.
This should work:
#Component
public class GerarSeloListener {
private final SeloService seloService;
#Autowired
public GerarSeloListener(SeloService seloService) { ... }
#EventListener
#Async
public void handleGerarSeloEvent(GerarSeloEvent event event) {
....
}
You need to add #EnableAsync on one of your configuration (the best place is your #SpringBootApplication annotated class). But as Martin already said you don't need event if you want to process a method asynchronously: only add #Async and invoke it the usual way.
You may want to read the documentation

Accessing Beans outside of the Step Scope in Spring Batch

Is it possible to access beans defined outside of the step scope? For example, if I define a strategy "strategyA" and pass it in the job parameters I would like the #Value to resolve to the strategyA bean. Is this possible? I am currently working round the problem by getting the bean manually from the applicationContext.
#Bean
#StepScope
public Tasklet myTasklet(
#Value("#{jobParameters['strategy']}") MyCustomClass myCustomStrategy)
MyTasklet myTasklet= new yTasklet();
myTasklet.setStrategy(myCustomStrategy);
return myTasklet;
}
I would like to have the ability to add more strategies without having to modify the code.
The sort answer is yes. This is more general spring/design pattern issue rater then Spring Batch.
The Spring Batch tricky parts are the configuration and understanding scope of bean creation.
Let’s assume all your Strategies implement Strategy interface that looks like:
interface Strategy {
int execute(int a, int b);
};
Every strategy should implements Strategy and use #Component annotation to allow automatic discovery of new Strategy. Make sure all new strategy will placed under the correct package so component scan will find them.
For example:
#Component
public class StrategyA implements Strategy {
#Override
public int execute(int a, int b) {
return a+b;
}
}
The above are singletons and will be created on the application context initialization.
This stage is too early to use #Value("#{jobParameters['strategy']}") as JobParameter wasn't created yet.
So I suggest a locator bean that will be used later when myTasklet is created (Step Scope).
StrategyLocator class:
public class StrategyLocator {
private Map<String, ? extends Strategy> strategyMap;
public Strategy lookup(String strategy) {
return strategyMap.get(strategy);
}
public void setStrategyMap(Map<String, ? extends Strategy> strategyMap) {
this.strategyMap = strategyMap;
}
}
Configuration will look like:
#Bean
#StepScope
public MyTaskelt myTasklet () {
MyTaskelt myTasklet = new MyTaskelt();
//set the strategyLocator
myTasklet.setStrategyLocator(strategyLocator());
return myTasklet;
}
#Bean
protected StrategyLocator strategyLocator(){
return = new StrategyLocator();
}
To initialize StrategyLocator we need to make sure all strategy were already created. So the best approach would be to use ApplicationListener on ContextRefreshedEvent event (warning in this example strategy names start with lower case letter, changing this is easy...).
#Component
public class PlugableStrategyMapper implements ApplicationListener<ContextRefreshedEvent> {
#Autowired
private StrategyLocator strategyLocator;
#Override
public void onApplicationEvent(ContextRefreshedEvent contextRefreshedEvent) {
ApplicationContext applicationContext = contextRefreshedEvent.getApplicationContext();
Map<String, Strategy> beansOfTypeStrategy = applicationContext.getBeansOfType(Strategy.class);
strategyLocator.setStrategyMap(beansOfTypeStrategy);
}
}
The tasklet will hold a field of type String that will be injected with Strategy enum String using #Value and will be resolved using the locator using a "before step" Listener.
public class MyTaskelt implements Tasklet,StepExecutionListener {
#Value("#{jobParameters['strategy']}")
private String strategyName;
private Strategy strategy;
private StrategyLocator strategyLocator;
#BeforeStep
public void beforeStep(StepExecution stepExecution) {
strategy = strategyLocator.lookup(strategyName);
}
#Override
public RepeatStatus execute(StepContribution contribution, ChunkContext chunkContext) throws Exception {
int executeStrategyResult = strategy.execute(1, 2);
}
public void setStrategyLocator(StrategyLocator strategyLocator) {
this.strategyLocator = strategyLocator;
}
}
To attach the listener to the taskelt you need to set it in your step configuration:
#Bean
protected Step myTaskletstep() throws MalformedURLException {
return steps.get("myTaskletstep")
.transactionManager(transactionManager())
.tasklet(deleteFileTaskelt())
.listener(deleteFileTaskelt())
.build();
}
jobParameters is holding just a String object and not the real object (and I think is not a good pratice store a bean definition into parameters).
I'll move in this way:
#Bean
#StepScope
class MyStategyHolder {
private MyCustomClass myStrategy;
// Add get/set
#BeforeJob
void beforeJob(JobExecution jobExecution) {
myStrategy = (Bind the right strategy using job parameter value);
}
}
and register MyStategyHolder as listener.
In your tasklet use #Value("#{MyStategyHolder.myStrategy}") or access MyStategyHolder instance and perform a getMyStrategy().

Resources