How to make saga starts - java

I'm trying to use Axon and Saga design pattern to send data between Micorservices using Java
I have two service one for Order and one for Product CQRS Design pattern works perfectly I mean I send data to axon and I see it in my dashboard and then by query data gets and save in read database (MySQL).
Today I tried to use Saga design pattern and when create a new order send ReserveProductCommand to axon and gets by Product service but saga doesn't event start and I don't know why
Below Saga class in Order service that should gets OrderCreatedEvent and Log the message but it doesn't
#Saga
public class OrderSaga {
private final transient CommandGateway commandGateway;
private static final Logger LOGGER = LoggerFactory.getLogger(OrderSaga.class);
#Autowired
public OrderSaga(CommandGateway commandGateway) {
this.commandGateway = commandGateway;
}
#StartSaga
#SagaEventHandler(associationProperty = "orderId")
public void handle(OrderCreatedEvent orderCreatedEvent) {
ReserveProductCommand reserveProductCommand = ReserveProductCommand
.builder()
.orderId(orderCreatedEvent.getOrderId())
.productId(orderCreatedEvent.getProductId())
.quantity(orderCreatedEvent.getQuantity())
.userId(orderCreatedEvent.getUserId())
.build();
LOGGER.info("OrderCreatedEvent handled for orderId: " + reserveProductCommand.getOrderId() + " and productId: " + reserveProductCommand.getProductId());
commandGateway.send(reserveProductCommand, new CommandCallback<ReserveProductCommand, Object>() {
#Override
public void onResult(CommandMessage<? extends ReserveProductCommand> commandMessage, CommandResultMessage<?> commandResultMessage) {
if (commandResultMessage.isExceptional()){
}
}
});
}
#SagaEventHandler(associationProperty = "orderId")
public void handle(ProductReservedEvent productReservedEvent){
LOGGER.info("ProductReservedEvent is called for productId: " + productReservedEvent.getProductId() + " and orderId: " + productReservedEvent.getOrderId());
}
}
Below OrderCreatedEvent
#Data
#AllArgsConstructor
#NoArgsConstructor
public class OrderCreatedEvent {
public String orderId;
private String userId;
private String productId;
private int quantity;
private String addressId;
private OrderStatus orderStatus;
}
I've seen axon dashboard OrderCreatedEvent has been published there

I think the problem is that Axon requires a no-arg constructor on Saga's. You should use field injection for the resources. This passage in the reference guide explains it:
The SpringResourceInjector uses Spring's dependency injection mechanism to inject resources into a Saga. This means you can use setter injection or direct field injection if you require. The method or field to be injected needs to be annotated in order for Spring to recognize it as a dependency, for example with #Autowired.
The passage can be found here: https://docs.axoniq.io/reference-guide/axon-framework/sagas/implementation. Changing the constructor injection to field injection, like the following sample, should work for you:
#Saga
public class OrderSaga {
#Autowired
private final transient CommandGateway commandGateway;
private static final Logger LOGGER = LoggerFactory.getLogger(OrderSaga.class);
// Abbreviated for clarity
}

Related

Any way to declare happen-before relatioship in Spring Boot?

I have some validation code that should run on server startup and make sure various conditions are met so whoever deploys the server don't messes up the DB or start the server with bad security configurations etc. To do that I created a bean
#Component
public class ApplicationStartupConditionsValidationBean {
static class ServerInitializationError extends Error{
public ServerInitializationError(String msg){
super(msg);
}
}
private Environment springEnv;
private String datasourceURL;
private String ddlAuto;
private static final Logger logger = LogManager.getLogger();
#Autowired
public ApplicationStartupConditionsValidationBean(Environment springEnv) throws Exception {
this.springEnv = springEnv;
this.datasourceURL = springEnv.getProperty("spring.datasource.url");
this.ddlAuto = springEnv.getProperty("spring.jpa.hibernate.ddl-auto");
validateStartupConditions();
}
public boolean isDBLocal(){
return datasourceURL.startsWith("jdbc:postgresql://localhost:");
}
private String disallowedParamMsg(String optionName, String optionValue){
return "option " + optionName + "=" + optionValue + " not allowed in production";
}
private void reject(String msg) throws ServerInitializationError{
String rejectionMsg = "startup conditions validation failed with msg: " + msg;
logger.error(rejectionMsg);
throw new ServerInitializationError(rejectionMsg);
}
private void reject(String paramName, String paramValue) throws ServerInitializationError{
reject(disallowedParamMsg(paramName, paramValue));
}
private void validateDatasourceParams(){
if(!isDBLocal() &&
!ddlAuto.equals("validate")){
reject("ddl-auto", ddlAuto);
}
}
public void validateStartupConditions() throws Exception{
logger.info("validating startup conditions");
validateDatasourceParams();
// more validation logic...
logger.info("startup conditions validation succeeded, proceeding with boot");
}
}
The way I would have wanted to use this class is to define what beans this must come before. In the example here I would have wanted to make sure this bean would be created before the DataSource bean is created, so that "ddl-auto=create" doesn't slip in production. I Know about the #DependsOn annotation but I would have wanted to do the reverse, and declare that this bean #HappensBefore a list of other beans. Is there any way to do this?
Thanks!
To run code before "normal" beans get created, you can use a BeanFactoryPostProcessor.
To declaratively add additional dependencies among beans, you could also use a BeanPostProcessor, but that sounds needlessly cumbersome for your use case.

How to use Mongo Auditing and a UUID as id with Spring Boot 2.2.x?

I would like to have Documents stored with an UUID id and createdAt / updatedAt fields. My solution was working with Spring Boot 2.1.x. After I upgraded from Spring Boot 2.1.11.RELEASE to 2.2.0.RELEASE my test for MongoAuditing failed with createdAt = null. What do I need to do to get the createdAt field filled again?
This is not just a testproblem. I ran the application and it has the same behaviour as my test. All auditing fields stay null.
I have a Configuration to enable MongoAuditing and UUID generation:
#Configuration
#EnableMongoAuditing
public class MongoConfiguration {
#Bean
public GenerateUUIDListener generateUUIDListener() {
return new GenerateUUIDListener();
}
}
The listner hooks into the onBeforeConvert - I guess thats where the trouble starts.
public class GenerateUUIDListener extends AbstractMongoEventListener<IdentifiableEntity> {
#Override
public void onBeforeConvert(BeforeConvertEvent<IdentifiableEntity> event) {
IdentifiableEntity entity = event.getSource();
if (entity.isNew()) {
entity.setId(UUID.randomUUID());
}
}
}
The document itself (I dropped the getter and setters):
#Document
public class MyDocument extends InsertableEntity {
private String name;
}
public abstract class InsertableEntity extends IdentifiableEntity {
#CreatedDate
#JsonIgnore
private Instant createdAt;
}
public abstract class IdentifiableEntity implements Persistable<UUID> {
#Id
private UUID id;
#JsonIgnore
public boolean isNew() {
return getId() == null;
}
}
A complete minimal example can be find here (including a test) https://github.com/mab/auditable
With 2.1.11.RELEASE the test succeeds with 2.2.0.RELEASE it fails.
For me the best solution was to switch from event UUID generation to a callback based one. With the implementation of Ordered we can set the new callback to be executed after the AuditingEntityCallback.
public class IdEntityCallback implements BeforeConvertCallback<IdentifiableEntity>, Ordered {
#Override
public IdentifiableEntity onBeforeConvert(IdentifiableEntity entity, String collection) {
if (entity.isNew()) {
entity.setId(UUID.randomUUID());
}
return entity;
}
#Override
public int getOrder() {
return 101;
}
}
I registered the callback with the MongoConfiguration. For a more general solution you might want to take a look at the registration of the AuditingEntityCallback with the `MongoAuditingBeanDefinitionParser.
#Configuration
#EnableMongoAuditing
public class MongoConfiguration {
#Bean
public IdEntityCallback registerCallback() {
return new IdEntityCallback();
}
}
MongoTemplate works in the following way on doInsert()
this.maybeEmitEvent - emit an event (onBeforeConvert, onBeforeSave and such) so any AbstractMappingEventListener can catch and act upon like you did with GenerateUUIDListener
this.maybeCallBeforeConvert - call before convert callbacks like mongo auditing
like you can see in source code of MongoTemplate.class src (831-832)
protected <T> T doInsert(String collectionName, T objectToSave, MongoWriter<T> writer) {
BeforeConvertEvent<T> event = new BeforeConvertEvent(objectToSave, collectionName);
T toConvert = ((BeforeConvertEvent)this.maybeEmitEvent(event)).getSource(); //emit event
toConvert = this.maybeCallBeforeConvert(toConvert, collectionName); //call some before convert handlers
...
}
MongoAudit marks createdAt only to new entities by checking if entity.isNew() == true
because your code (UUID) already set the Id the createdAt is not populated (the entity is not considered new)
you can do the following (order by best to worst):
forget about the UUID and use String for your id, let the mongo itself create and manage it's entities ids (this how MongoTemplate actually works lines 811-812)
keep the UUID at the code level, convert from/to String when inserting and retrieving from the db
create a custom repository like in this post
stay with 2.1.11.RELEASE
set the updateAt by GenerateUUIDListener as well as id (rename it NewEntityListener or smth), basically implement the audit
implement a new isNew() logic that don't depends only on the entity id
in version 2.1.11.RELEASE the order of the methods was flipped (MongoTemplate.class 804-805) so your code worked fine
as an abstract approach, the nature of event is to be sort of send-and-forget (async compatible), so it's a very bad practice to change the object itself, there is NO grantee for order of computation, if any
this is why the audit build on callbacks and not events, and that's why Pivotal don't (need to) keep order between versions

Best way to dynamically resolve dependencies in Java Spring?

Let's say I have this code structure:
public class NotificationService {
public void send(Notification notification) {
// call other services and send the notification
}
}
public class OrderNotification implements Notification {
#Autowired
public TranslationService translationService;
private String orderNumber;
public OrderNotification(String orderNumber) {
this.orderNumber = orderNumber;
}
public String getMessage() {
return translationService.trans('notification.order', new Object[]{orderNumber});
}
}
So, my goal is to use the NotificationService in this way:
notificationService.send(new OrderNotification(orderNumber));
But I know that code above won't work, because of the translationService won't be resolved.
My goal is to pass custom parameters to my Notification classes and being able to use services inside that class. What is the best way to do it in the Spring?
I know that below is not the correct answer to your question. It is however a bad design pattern to combine Entities and Services. An Entity should only contain information about the object and not business logic. A Service contains all the business logic.
You need to separate your Service from your Entity.
OrderNotification looks like a regular entity. The entity should not contain business logic. You need a specific service for the business logic.
public class OrderNotification implements Notification {
private String orderNumber;
public OrderNotification(String orderNumber) {
this.orderNumber = orderNumber;
}
public String getMessage() {
return "Order number: " + orderNumber;
}
//Getter & Setters
...
}
#Service
public class NotificationService {
#Autowired
public TranslationService translationService;
public void send(Notification notification) {
//I do not know what trans accepts, so I assume it can accept Notification
translationService.trans(notification.getMessage());
}
}
If you really need to combine the entity and service - Then I recommend this approach:
#Service
public class Master{
#Autowired
NotificationService notificationService
public void testMethod(){
Notification notification = notificationService.createOrder("order1");
notificationService.send(notification);
}
}
#Service
public class NotificationService {
#Autowired
public TranslationService translationService;
public Notification createOrder(String orderNumber){
return new OrderNotification(orderNumber, translationService);
}
public void send(Notification notification) {
// call other services and send the notification
notification.getMessage();
}
}
public class OrderNotification implements Notification {
private TranslationService translationService;
private String orderNumber;
//I have changed this constructor to accept TranslationService.
public OrderNotification(String orderNumber, TranslationService translationService) {
this.orderNumber = orderNumber;
this.translationService = translationService;
}
public String getMessage() {
return translationService.trans('notification.order', new Object[]{orderNumber});
}
}
You have few options available:
Configure AOP and load time weaving to process Spring annotations on objects created with new keyword. This is explained in the docs 5.8.1. Using AspectJ to dependency inject domain objects with Spring.
Declare OrderNotification as a prototype scoped bean and obtain each instance from the context using BeanFactory.getBean(Class<T> requiredType, Object... args) method.
String orderNumber = "123";
OrderNotificaton = factory.getBean(OrderNotificaton.class, orderNumber);
Drop the #Autowired and use plain constructor injection.
public OrderNotification(TranslationService translationService, String orderNumber) {
this.translationService = Objects.requireNonNull(translationService);
this.orderNumber = Objects.requireNonNull(orderNumber);
}
If you only require simple #Autowired I'd go with option 3. It's the simplest approach and makes writing unit tests easier as you don't have to depend on Spring.

MyBatis #Mapper declaration

Spring Boot | MyBatis
When I try to declare a mybatis mapper in controller, it gets underlined by IDE, and doesn't compile.
#Controller
#RequestMapping("demo")
#MapperScan("com.sample.mapper")
public class MessageController {
private static final String MESSAGE = "message";
private static final String INDEX = "index";
#Autowired
private MessageMapper messageMapper;
#RequestMapping("printMessage/{message}")
public String printMessage(ModelMap modelMap) {
modelMap.addAttribute(MESSAGE, "M");
return INDEX;
}
#RequestMapping("printHello")
public String printHello(ModelMap modelMap) {
modelMap.addAttribute(MESSAGE, "Hello, ");
return INDEX;
}
I got this class compiled somehow recently, however, when I try to use messageMapper instance, like messageMapper.insert() as it's not assigned any value, it gives me NullPointerException. It seems like Spring is for some reason is not working for me.
According to the documencation, I think the #MapperScan is not the right class, they cannot be autowired because they are not in the context on controller creation time. When it is defined in Mybatis XML config file, it is loaded with an Sql Session Factory Provider, a place that actually makes more sense, then it shall not be different with annotations style.

Spring Generic Dao class name

I have configured a custom generic service DAO for my spring / hibernate project - the idea being that I can reuse it easily from my controllers.
It essentially looks like this:
public class DefaultService<T> {
private Class<T> e;
public String className(Class<T> e) {
String clip = e.getName();
clip = clip.substring(clip.lastIndexOf('.') + 1, clip.length());
return clip;
}
public List<T> getAll(Integer status) {
Session session = sessionFactory.getCurrentSession();
Query query = session.createQuery("FROM " + className(e) + " WHERE status = " + status);
return query.list();
}
...
Which gets referenced by:
#Autowired
public DefaultService<Address> addressService;
addressService.get(1);
However the String clip = e.getName() line throws a Null pointer exception. I can get this to work if I move the class into the attributes section (so addressService.get(Address.class, 1) but I find this somewhat untidy, especially when there are multiple different classes being called upon.
Is there some way to get the class to generate a value correctly without repeatedly adding it into all my functions?
Thanks in advance.
I did something similar, you need the generic class to be a constructor argument as well, mine uses hibernate entities, but you could pass in the string of table name.
public class DomainRepository<T> {
#Resource(name = "sessionFactory")
protected SessionFactory sessionFactory;
public DomainRepository(Class genericType) {
this.genericType = genericType;
}
#Transactional(readOnly = true)
public T get(final long id) {
return (T) sessionFactory.getCurrentSession().get(genericType, id);
}
You can then subclass (if you need to) to customize or simply set up you bean in the spring config like below t :
<bean id="tagRepository" class="com.yourcompnay.data.DomainRepository">
<constructor-arg value="com.yourcompnay.domain.Tag"/>
</bean>
So in your code you could then reference tagRepository like so (no other cod eis needed than that posted above, and below) :
#Resource(name = "tagRepository")
private DomainRepository<Tag> tagRepository;
Also, I would call it a repository not a service, a service deals with different types and their interactions (not just one). And for specifically your example using SQL strings :
public final String tableName;
public DomainRepository(String tableName) {
this.tableName = tableName;
}
public List<T> getAll(Integer status) {
Session session = sessionFactory.getCurrentSession();
Query query = session.createQuery("FROM " + tableName + " WHERE status = " + status);
return query.list();
}
and have your beans defined like so
<bean id="addressRepository" class="com.yourcompnay.data.DomainRepository">
<constructor-arg value="address"/>
</bean>
And then you can alsow create subclasses youself where necessary :
public class PersonRepository extends DomainRepository<Person> {
public PersonRepository(){
super("person"); //assumes table name is person
}
As I understand you got NPE because you did not set any value for this field.
So you can resolve this problem by 2 ways:
Set manually class object as in comment NimChimpsky.
Get class type dynamically. E.g, if you use Spring try this one:
protected Class getEntityClass() {
return GenericTypeResolver.resolveTypeArguments(getClass(), DefaultService.class)[0];
}
or some workaround here
It's better to define a specific class for Address service
public class AddressService extends DefaultService<Address>{
public String getClassName(){
return "Address";
}
}
where
public String getClassName();
is an abstract method declared in DefaultService, and used (like your method className()) in your data access logic.
Using this approach, you will be able to add specific data access logic (example, getUsersByAddress)

Categories