Axon CQRS & EventSourcing deep validation in the db - java

I am migrating and existing crud application into Axon, and I have some concerns with the following scenario. I have an api given below to create groups,
#PostMapping
public Mono<IdDto> createGroup(#RequestBody #Valid CreateGroupCommand command) {
log.trace("Create GroupResponseInfoDto request {}", command);
return commandGateway.send(command)
.map((o) -> new IdDto((UUID) o));
}
The command looks like,
#Data
public class CreateGroupCommand {
#NotBlank
private String name;
#NotBlank
private String description;
}
and the main requirement here is that the group name must be unique.
Therefore, in the Aggregate I have the following code to check this logic.
#CommandHandler
public GroupAggregate(CreateGroupCommand command, GroupRepository groupRepository, ModelMapper mapper) {
log.trace("Handle create group command {}", command);
groupRepository.findByName(command.getName())
.ifPresent((g) -> {
throw new ApplicationException(UserMsError.GROUP_ALREADY_EXISTS.name());
});
GroupCreatedEvent event = mapper.map(command, GroupCreatedEvent.class);
event.setId(UUID.randomUUID());
AggregateLifecycle.apply(event);
}
And once the validation pareses, the event is persisted by a projector into the db.
#EventSourcingHandler
public void on(GroupCreatedEvent event) {
log.trace("Group create event {}", event);
groupRepository.findByName(event.getName())
.ifPresent((g) -> {
throw new ApplicationException(UserMsError.GROUP_ALREADY_EXISTS.name());
});
Group group = modelMapper.map(event, Group.class);
groupRepository.save(group);
}
The problem now is, there is some lap time between the command execution and the persistance of the event results into group table. If another user creates a group in that time, the command does not fail as the record does not exist in the db. Now, I see in Axon site there is a way to create a temporary table where we put the command execution into some temporary table which we can use for validation purpose, but that requires additional coding and quite extra effort for each such requirement. It also means, if we persist the details on command execution, and for some reason the process fails then the record will exist in our validation table but not on the system. If we try to validate the scenario on event execution, that extra effort might not be required but in this case the problem is I am not able to fail the API call so that the user knows the results. Could you please recommend if there is an alternative approach to validate the input without an intermediate check?

The problem you are looking at is set-based validation. Whenever you're dealing with CQRS, it's the sets that will require extra work to be validated.
Although uncertain, I assume you're talking about the Set-Based Consistency Validation blog? That is, for a reason, the suggested approach to deal with set validation. Note that the implementation used in the blog can be found here.
Added, it quite recently has seen an update that does not include the problem you describe as follows:
It also means, if we persist the details on command execution, and for some reason, the process fails, then the record will exist in our validation table but not on the system.
Axon's transaction logic, supported through the UnitOfWork, will roll back the entire transaction if something fails. This thus anything you'd do inside the UnitOfWork, including updates to another table for validation.
I get that it's some boilerplate code, but it is the predicament of having the uniqueness requirement on a set. What might be something you can look into is forcing the uniqueness through the Aggregate Identifier. Axon's Event Store logic ensures no two events are using the same aggregate identifier. So, if you try to input a new aggregate (hence a new event) for an already existing aggregate identifier, the operation will fail.
This approach is typically not feasible whenever the set-based consistency validation issue is described, though, so I am guessing it won't help you out.
Concluding, I'd take your win from the shared repository on the blog to minimize your personal effort on the matter.

Related

Managing Concurrent POST-Requests Best Practice in SpringBoot

I am building a REST-API with SpringBoot and using this Controller.
#RestController
class EmployeeController {
private final EmployeeRepository repository;
EmployeeController(EmployeeRepository repository) {
this.repository = repository;
}
#GetMapping("/employees")
List<Employee> all() {
return repository.findAll();
}
#PostMapping("/employees")
Employee newEmployee(#RequestBody Employee newEmployee) {
return repository.save(newEmployee);
}
I want to ensure that API-Consumers cannot spam multiple concurrent POST-Requests with the same Employee. I know that I can check if the entity already exists in the database before saving it, but I am afraid that the performance will be bad. I also already noticed that you can use Annotation like #version in your entity, to make updates on existing Entity`s more save.
But is there also a way or a best practice in Spring how to handle this POST-Requests with a potential new Entity?
What kind of request throughput are you expecting to the POST /employees endpoint? While performance is important, premature optimization is almost always going to cause your code to be uglier than it needs to for little gain.
As your code currently stands, multiple concurrent POST /employees requests would end up with a first-come first-serve basis where the first user with the given UNIQUE constraint in your application (which is hopefully enforced by your underlying DBMS) is created, and all other after that (for the same user) would fail due to a e.g. ConstraintViolationException (mapped to a e.g. DataIntegrityViolationException). From this point of view (as long as you do not have a complicated distributed DBMS setup), the consistency of the data is still guaranteed.
The downside, of course, is that the error messages that would be returned would be:
Vendor-specific and leak the underlying implementation (e.g. we're showing the client that we're using Hibernate)
Potentially difficult for the client to parse.
If you instead, change the implementation to something like the following:
#PostMapping("/employees")
Employee newEmployee(#RequestBody Employee newEmployee) {
verifyEmployeeDoesNotExist(newEmployee);
return repository.save(newEmployee);
}
private void verifyUserDoesNotExist(Employee employee) {
if (repository.exists(newEmployee) {
throw new EmployeeAlreadyExistsException("Employee " + newEmployee.getName() + " already exists";
}
}
then you could more easily control the control flow of your endpoint and the underlying process, which would potentially allow for more easily-digestible exception handling. This could be even further improved by adding e.g. custom exceptions which also contain some code of pre-defined error code such as e.g. error 409 code 1010 Employee already exists.
Of course, Spring's built-in exception translation for Hibernate (e.g. HibernateExceptionTranslator) might already be good enough for your use-case and could even be extended and this extension can be even generalized.
In the end, the best practice is making your code clean, readable and maintainable. Then start adding functionality to monitor your code. After that, and only if you have a problem with performance, you can still optimize it.

Call method post transaction commit

I have a very peculiar requirement where I have to insert records in 2 tables (audit tables) if insertion in one particular table succeeds. Here I am not talking about #PreInsert in Listener because Listeners are always called in the same transaction. I know that can be done manually by simply calling "save" method after the first save succeeds. BUT I wanted to know is there any other way which I can try using Listener be it JPA/EclipseLink/String-data so that future developers of the application are not forced to insert data in audit table manually.
Basically I am looking for #PostCommit type of functionality. Please help me.
I believe you ultimately do want the callback to run within the boundary of your current transaction, you just want it to run after Hibernate has done its things, just like Hibernate Envers works.
To do this, you basically need to register an event action queue callback like the following:
session.getActionQueue().registerProcess(
new BeforeTransactionCompletionProcess() {
#Override
public void doBeforeTransactionCompletion(SessionImplementor session) {
// do whatever you want with the session here.
}
}
);
If you ultimately must run your code outside the transaction, you could do something similar:
session.getActionQueue().registerProcess(
new AfterTransactionCompletionProcess() {
#Override
public void doAfterTransactionCompletion(boolean success, SharedSessionContractImplementor session) {
// do whatever you want with the session here.
}
}
);
That should get you going either way.

Gemfire EntryNotFoundException for #CacheEvict

In short, when #CacheEvict is called on a method and if the key for the entry is not found, Gemfire is throwing EntryNotFoundException.
Now in detail,
I have a class
class Person {
String mobile;
int dept;
String name;
}
I have two Cache regions defined as personRegion and personByDeptRegion and the Service is as below
#Service
class PersonServiceImpl {
#Cacheable(value = "personRegion")
public Person findByMobile(String mobile) {
return personRepository.findByMobile(mobile);
}
#Cacheable(value = "personByDeptRegion")
public List<Person> findByDept(int deptCode) {
return personRepository.findByDept(deptCode);
}
#Caching(
evict = { #CacheEvict(value = "personByDeptRegion", key="#p0.dept"},
put = { #CachePut(value = "personRegion",key = "#p0.mobile")}
)
public Person updatePerson(Person p1) {
return personRepository.save(p1);
}
}
When there is a call to updatePerson and if there are no entries in the personByDeptRegion, this would throw an exception that EntryNotFoundException for the key 1 ( or whatever is the dept code ). There is a very good chance that this method will be called before the #Cacheable methods are called and want to avoid this exception.
Is there any way we could tweak the Gemfire behavior to gracefully return when the key is not existing for a given region ?.
Alternatively, I am also eager to know if there is a better implementation of the above scenario using Gemfire as cache.
Spring Data Gemfire : 1.7.4
Gemfire Version : v8.2.1
Note: The above code is for representation purpose only and I have multiple services with same issue in actual project.
First, I commend you for using Spring's Caching annotations on your application #Service components. All too often developers enable caching in their Repositories, which I think is bad form, especially if complex business rules (or even additional IO; e.g. calling a web service from a service component) are involved prior to or after the Repository interaction(s), particularly in cases where caching behavior should not be affected (or determined).
I also think your caching UC (updating one cache (personRegion) while invalidating another (personByDeptRegion) on a data store update) by following a CachePut with a CacheEvict seems reasonable to me. Though, I would point out that the seemingly intended use of the #Caching annotation is to combine multiple Caching annotations of the same type (e.g. multiple #CacheEvict or multiple #CachePut) as explained in the core Spring Framework Reference Guide. Still, there is nothing preventing your intended use.
I created a similar test class here, modeled after your example above, to verify the problem. Indeed the jonDoeUpdateSuccessful test case fails (with the GemFire EntryNotFoundException, shown below) since no people in Department "R&D" were previously cached in the "DepartmentPeople" GemFire Region prior to the update, unlike the janeDoeUpdateSuccessful test case, which causes the cache to be populated before the update (even if the entry has no values, which is of no consequence).
com.gemstone.gemfire.cache.EntryNotFoundException: RESEARCH_DEVELOPMENT
at com.gemstone.gemfire.internal.cache.AbstractRegionMap.destroy(AbstractRegionMap.java:1435)
NOTE: My test uses GemFire as both a "cache provider" and a System of Record (SOR).
The problem really lies in SDG's use of Region.destroy(key) in the GemfireCache.evict(key) implementation rather than, and perhaps more appropriately, Region.remove(key).
GemfireCache.evict(key) has been implemented with Region.destroy(key) since inception. However, Region.remove(key) was not introduced until GemFire v5.0. Still, I can see no discernible difference between Region.destroy(key) and Region.remove(key) other than the EntryNotFoundException thrown by Region.destroy(key). Essentially, they both destroy the local entry (both key and value) as well as distribute the operation to other caches in the cluster (providing a non-LOCAL Scope is used).
So, I have filed SGF-539 to change SDG to call Region.remove(key) in GemfireCache.evict(key) rather than Region.destroy(key).
As for a workaround, well, there is basically only 2 things you can do:
Restructure your code and your use of the #CacheEvict annotation, and/or...
Make use of the condition on #CacheEvict.
It is unfortunate that a condition cannot be specified using a class type, something akin to a Spring Condition (in addition to SpEL), but this interface is intended for another purpose and the #CacheEvict, condition attribute does not accept a class type.
At the moment, I don't have a good example of how this might work so I am moving forward on SGF-539.
You can following this ticket for more details and progress.
Sorry for the inconvenience.
-John

How can I pass a customized value to a spring aop advice?

If I take a service method named public void delete(int id); as the pointcut, I want to add an after-returning advice on it, but I don't know what kind of object was deleted(however, the servlet which called the service knows the type value), so I was wondering if I can pass a customized value to this after-returning advice when it is activated, like 'user'. I've already checked the related document on Spring's website and I still know nothing. I'd appreciate your answer, THX.
One solution but its required refactoring in Service method
1) Refactoring
public class DeleteRequest {
String type;
Long id;
}
public boolean delete(DeleteRequest request){ // impl}
2) Use Around Advice
2.1) Before proceeding method execution, read passed parameter & get to be deleted object for "XYZ" requirement.
2.2) Capture result of delete method execution
IF its TRUE then DO your stuff
Note: I used this approach for deleted entity notification. So how we can get deleted entity information in after-advice, hence keep it entity information in before phase & use it in after-successful execution.

Seam/Hibernate validator listener

We use a standard SEAM setup here ... complete with the validation system that uses hibernate.
Basically what happens is a user enters a value into an html input and seam validates the value they entered using the hibernate validation.
Works fine for the most part except here's my problem: We need to record the results of validation on each field and I can't figure out a good way to do it ... ideally it would be done through communicating with the seam/hibernate validation system and just recording the validation results but as far as I can tell there isn't a way to do this?
Has anyone done anything like this in the past? There are a couple nasty work arounds but I'd prefer to do it cleanly.
Just a quick overview of the process that we have happening right now for context:
1) user enters field value
2) onblur value is set with ajax (a4j:support) at this point the validators fire and the div is re-rendered, if any validation errors occured they're now visible on the page
What I'd like to have happen at step2 is a 'ValidationListener' or something similar is called which would allow us to record the results of the validation.
Thanks if anyone is able to help :o
You should be able to do it by creating a Bean that has a method observing the org.jboss.seam.validationFailed event. That method can then do whatever logging you want.
#Name("validationObserver")
public class ValidationObserver() {
#Observer("org.jboss.seam.validationFailed")
public void validationFailed() {
//Do stuff
}
}
The validationFailed event doesn't pass any parameters so you'll have to interrogate the FacesMessages or possibly the Hibernate Validation framework itself if you want to record what the error was.
I you are only using Hibernate for validation, you can use the Hibernate ClassValidator in the validationFailed() method, as recommended by Damo.
Example:
public <T> InvalidValue[] validateWithHibernate(T object) {
ClassValidator<T> validator = new ClassValidator(object.getClass());
InvalidValue[] invalidValues = validator.getInvalidValues(object);
return invalidValues;
}

Categories