Is my implementation of a simple CRUD service with Spring WebFlux correct? - spring

I am implementing a simple Movie based CRUD API using Spring WebFlux and Reactive Spring Data MongoDB. I want to make sure that my implementation is correct and that I am properly using Flux and Mono to implement the CRUD operations. I also want to make sure that I am properly handling any errors or null values. I am very new to this programming paradigm and Spring WebFlux, so I am not sure about the correctness of the implementation of the Controller and Service layer, I want to make sure I am adhering to Spring WebFlux and Project Reactor best practices.
#Repository
public interface MovieRepository extends ReactiveMongoRepository<Movie, String> {
Flux<Movie> findByRating(String rating);
}
public interface MovieService {
Flux<Movie> list();
Flux<Movie> findByRating(String rating);
Mono<Movie> update(String id, MovieRequest movieRequest);
Mono<Movie> create(Mono<MovieRequest> movieRequest);
Mono<Movie> read(String id);
Mono<Movie> delete(String id);
}
#Service
public class MovieServiceImpl implements MovieService {
#Autowired
private MovieRepository movieRepository;
#Override
public Flux<Movie> list(){
return movieRepository.findAll();
}
#Override
public Flux<Movie> findByRating(final String rating){
return movieRepository.findByRating(rating);
}
#Override
public Mono<Movie> update(String id, MovieRequest movieRequest) {
return movieRepository.findOne(id).map(existingMovie -> {
if(movieRequest.getDescription() != null){
existingMovie.setDescription(movieRequest.getDescription());
}
if(movieRequest.getRating() != null){
existingMovie.setRating(movieRequest.getRating());
}
if(movieRequest.getTitle() != null) {
existingMovie.setTitle(movieRequest.getTitle());
}
return existingMovie;
}).then(movieRepository::save);
}
#Override
public Mono<Movie> create(Mono<MovieRequest> movieRequest) {
return movieRequest.map(newMovie -> {
Movie movie = new Movie();
if(newMovie.getDescription() != null){
movie.setDescription(newMovie.getDescription());
}
if(newMovie.getRating() != null){
movie.setRating(newMovie.getRating());
}
if(newMovie.getTitle() != null) {
movie.setTitle(newMovie.getTitle());
}
return movie;
}).then(movieRepository::save);
}
#Override
public Mono<Movie> read(String id) {
return movieRepository.findOne(id);
}
#Override
public Mono<Movie> delete(String id) {
Mono<Movie> movie = movieRepository.findOne(id);
movieRepository.delete(id);
return movie;
}
}
#RestController
public class MovieRestController {
#Autowired
private MovieService movieService;
#GetMapping(value = "/movies")
public Flux<ResponseEntity<Movie>> list() {
return movieService.list().map(m -> new ResponseEntity<>(m, HttpStatus.OK));
}
#GetMapping(value = "/moviesByRating")
public Flux<ResponseEntity<Movie>> findByRating(
#RequestParam(value = "rating", required = false) final String rating) {
return movieService.findByRating(rating)
.map(m -> new ResponseEntity<>(m, HttpStatus.OK));
}
#GetMapping("/movies/{movieId}")
public Mono<ResponseEntity<Movie>> read(
#PathVariable("movieId") final String movieId) {
return movieService.read(movieId)
.map(m -> new ResponseEntity<>(m, HttpStatus.OK));
}
#DeleteMapping("/movies/{movieId}")
public Mono<ResponseEntity<Movie>> delete(
#PathVariable("movieId") final String movieId) {
return movieService.delete(movieId)
.map(m -> new ResponseEntity<>(m, HttpStatus.OK));
}
#PutMapping("/movies/{movieId}")
public Mono<ResponseEntity<Movie>> update(
#PathVariable("movieId") final String movieId,
#RequestBody final MovieRequest movieRequest) {
return movieService.update(movieId, movieRequest)
.map(m -> new ResponseEntity<>(m, HttpStatus.OK));
}
#PostMapping("/movies")
public Mono<ResponseEntity<Movie>> create(
#RequestBody final Mono<MovieRequest> movieRequest) {
return movieService.create(movieRequest)
.map(m -> new ResponseEntity<>(m, HttpStatus.OK));
}
}

Related

Issue while dealing refactoring some code to rich-domain model

I was reading about rich vs anemic domain models and wanted to convert an existing anemic domain model into a rich domain model. However, springboot dependecy injection is in the way. How can I fix this? What is the correct approach to refactor an anemic domain model to a rich domain model?
I though about adding a public default constructor but wouldn't that violate the rules for rich-domain models and also it would mean that I can't mark gameId/the list of scores as final?
Error
Parameter 0 of constructor in Score required a bean of type 'java.lang.String' that could not be found.
no qualifying bean of type 'java.lang.String'
Original
#Service
public class ScoreService {
private final Map<String, List<BatsmanScore>> scores;
public ScoreService(Map<String, List<BatsmanScore>> scores) {
this.scores = scores;
}
public List<BatsmanScore> getBatsmanScores(String gameId) {
return scores.get(gameId);
}
public void storeScores(String gameId, List<BatsmanScore> batsmanScores) {
if (!scores.containsKey(gameId)) {
scores.put(gameId, new ArrayList<>());
}
scores.get(gameId).addAll(batsmanScores);
}
}
public class Score {
private List<BatsmanScore> batsmanScores;
private String gameId;
public Score() { }
public Score(String gameId, List<BatsmanScore> batsmanScores) {
this.gameId = gameId;
this.batsmanScores = batsmanScores;
}
public List<BatsmanScore> getBatsmanScores() {
return batsmanScores;
}
public String getGameId() {
return gameId;
}
}
Modified
#Service
public class ScoreService {
private final Score score;
public ScoreService(Score score) {
this.score = score;
}
public List<BatsmanScore> getBatsmanScores(String gameId) {
return score.getBatsmanScoreByGameId(gameId);
}
public void storeScores(String gameId, List<BatsmanScore> batsmanScores) {
score.storeGameScore(gameId, batsmanScores);
}
}
#Repository
public class Score {
private final Map<String, List<BatsmanScore>> scoreRepository = new HashMap<>();
private final List<BatsmanScore> batsmanScores;
private final String gameId;
public Score(String gameId, List<BatsmanScore> batsmanScores) {
this.gameId = gameId;
this.batsmanScores = batsmanScores;
}
public List<BatsmanScore> getBatsmanScores() {
return batsmanScores;
}
public List<BatsmanScore> getBatsmanScoresByGameId(String gameId) {
return scoreRepository.get(gameId);
}
public String getGameId() {
return gameId;
}
public void storeScores(String gameId, List<BatsmanScore> batsmanScores) {
if (!scoreRepository.containsKey(gameId)) {
scoreRepository.put(gameId, new ArrayList<>());
}
scoreRepository.get(gameId).addAll(batsmanScores);
}
}
Anemic domain model is when the business logic is in the services, but here it already is in the domain model, in the form of the Score.addAll() method:
public class Score {
private List<BatsmanScore> batsmanScores;
private String gameId;
public Score() { }
public Score(String gameId, List<BatsmanScore> batsmanScores) {
this.gameId = gameId;
this.batsmanScores = batsmanScores;
}
public List<BatsmanScore> getBatsmanScores() {
return batsmanScores;
}
public String getGameId() {
return gameId;
}
public void addAll(List<BatsmanScore> batsmanScores)
{
if (this.batsmanScores == null) {
this.batsmanScores = new ArrayList<BatsmanScore>();
}
foreach(BatsmanScore score in batsmanScores) {
// you can implement some more business logic here
this.batsmanScores.Add(score);
}
}
}
However, your are messing your repository and your service. The service usually handles the use case and orchestrate operations around the repository and the domain model:
#Service
public class AddScoreUseCase {
private final IScoreRepository repository;
public AddScoreUseCase(IScoreRepository repository) {
this.repository = repository;
}
public void storeScores(String gameId, List<BatsmanScore> batsmanScores) {
Score score = repository.getByGameId(gameId);
score.addAll(batsmanScores);
repository.saveChanges(score);
}
}
The repository is responsible to translate domain operations into the persistence. Here is an example of an in-memory persistence but you could also write a database repository, that would retrieve and store data from the database:
#Repository
public class InMemoryScoreRepository implements IScoreRepository {
private final Map<String, List<BatsmanScore>> scoreRepository = new HashMap<>();
public InMemoryScoreRepository() {
}
public Score getByGameId(String gameId) {
if (!data.containsKey(gameId)) {
return new Score(gameId, new ArrayList<>());
}
return new Score(gameId, this.data[gameId]);
}
public void saveChanges(Score score) {
if (!this.data.containsKey(score.getGameId())) {
scoreRepository.put(score.getGameId(), score.getBatsmanScores());
} else {
this.data[score.getGameId()] = score.getBatsmanScores();
}
}
}

Use custom inheritor from the hateoas.CollectionModel in hateoas.server.RepresentationModelProcessor

I have simple RestController that return CollectionModel:
#RequestMapping("/test")
public ResponseEntity<?> index() {
List<DemoEntity> all = Arrays.asList(new DemoEntity(1L, "first"),
new DemoEntity(2L, "second"),
new DemoEntity(3L, "third"));
List<EntityModel<DemoEntity>> list = new ArrayList<>();
all.forEach(demoEntity -> list.add(new EntityModel<>(demoEntity)));
CollectionModel<EntityModel<DemoEntity>> collectionModel = new CollectionModel<>(list);
return ResponseEntity.ok(collectionModel);
}
DemoEntity has only two fields, id and name. SecondEntity is the same.
I am trying to use RepresentationModelProcessor:
#Configuration
public class SpringDataRestConfig {
#Bean
public RepresentationModelProcessor<EntityModel<DemoEntity>> demoEntityProcessor() {
return new RepresentationModelProcessor<EntityModel<DemoEntity>>() {
#Override
public EntityModel<DemoEntity> process(EntityModel<DemoEntity> entity) {
return new MyHateoasEntityModel<>(entity.getContent(), entity.getLink("self").orElse(new Link("self")));
}
};
}
#Bean
public RepresentationModelProcessor<CollectionModel<EntityModel<DemoEntity>>> demoEntitiesProcessor() {
return new RepresentationModelProcessor<CollectionModel<EntityModel<DemoEntity>>>() {
#Override
public CollectionModel<EntityModel<DemoEntity>> process(CollectionModel<EntityModel<DemoEntity>> collection) {
// return new CollectionModel<>(collection.getContent());
return new MyHateoasCollectionModel<>(collection.getContent());
}
};
}
#Bean
public RepresentationModelProcessor<EntityModel<SecondEntity>> secondEntityProcessor() {
return new RepresentationModelProcessor<EntityModel<SecondEntity>>() {
#Override
public EntityModel<SecondEntity> process(EntityModel<SecondEntity> entity) {
return new MyHateoasEntityModel<>(entity.getContent(), entity.getLink("self").orElse(new Link("self")));
}
};
}
#Bean
public RepresentationModelProcessor<CollectionModel<EntityModel<SecondEntity>>> secondEntitiesProcessor() {
return new RepresentationModelProcessor<CollectionModel<EntityModel<SecondEntity>>>() {
#Override
public CollectionModel<EntityModel<SecondEntity>> process(CollectionModel<EntityModel<SecondEntity>> collection) {
// return new CollectionModel<>(collection.getContent());
return new MyHateoasCollectionModel<>(collection.getContent());
}
};
}
}
One thing here is that I want to use my own classes MyHateoasEntityModel and MyHateoasCollectionModel. They are quite simple:
public class MyHateoasEntityModel<T> extends EntityModel<T> {
private T entity;
public MyHateoasEntityModel(T entity, Iterable<Link> links) {
super(entity, Collections.emptyList());
this.entity = entity;
}
public MyHateoasEntityModel(T entity, Link... links) {
this(entity, Collections.emptyList());
}
}
public class MyHateoasCollectionModel<T> extends CollectionModel<T> {
public MyHateoasCollectionModel(Iterable<T> content, Link... links) {
super(content, Collections.emptyList());
}
}
The question is that when the controller is called, the demoEntityProcessor, demoEntitiesProcessor methods are called in turn. And this is what i want from the application. But then, somehow, secondEntitiesProcessor is called, but shouldn't.
Earlier, spring boot 1.5.17 was used, and everything works fine.
All code on: https://github.com/KarinaPleskach/Hateoas

Springboot Application Failed to start because of a bean problem

I am studying basic development practice.
While creating a CRUD Service and trying to abstract the service, an error related to the bean occurred.
here is my code
1.CrudInterface
public interface CrudInterface<Req, Res> {
Header<Res> create(Header<Req> request);
Header<Res> read(Long id);
Header<Res> update(Header<Req> request);
Header delete(Long id);
}
2.CrudController.java
#Component
public class CrudController<Req,Res,Entity> implements
CrudInterface<Req,Res> {
#Autowired(required = false)
protected BaseService<Req,Res,Entity> entityBaseService;
#Override
#PostMapping("")
public Header<Res> create(#RequestBody Header<Req> request) {
return entityBaseService.create(request);
}
#Override
#GetMapping("{id}")
public Header<Res> read(#PathVariable Long id) {
return entityBaseService.read(id);
}
#Override
#PutMapping("")
public Header<Res> update(#RequestBody Header<Req> request) {
return entityBaseService.update(request);
}
#Override
#DeleteMapping("{id}")
public Header delete(#PathVariable Long id) {
return entityBaseService.delete(id);
}
}
3.UserApiController.java
#Slf4j
#RestController
#RequestMapping("/api/user")
public class UserApiController extends
CrudController<UserApiRequest, UserApiResponse, User> {
}
4.BaseService.java
#Component
public abstract class BaseService<Req,Res,Entity> implements
CrudInterface<Req,Res> {
#Autowired(required = false)
protected JpaRepository<Entity,Long> baseRepository;
}
5.UserApiLogicService
#Service
public class UserApiLogicService extends
BaseService<UserApiRequest, UserApiResponse,User> {
#Override
public Header<UserApiResponse> create(Header<UserApiRequest> request) {
UserApiRequest body = request.getData();
User user = User.builder()
.account(body.getAccount())
.password(body.getPassword())
.status(UserStatus.REGISTERED)
.phoneNumber(body.getPhoneNumber())
.email(body.getEmail())
.registeredAt(LocalDateTime.now())
.build();
User newUser = baseRepository.save(user);
return response(newUser);
}
#Override
public Header<UserApiResponse> read(Long id) {
return baseRepository.findById(id)
.map(user -> response(user))
.orElseGet(
() -> Header.ERROR("데이터 없음")
);
}
#Override
public Header<UserApiResponse> update(Header<UserApiRequest> request) {
UserApiRequest body = request.getData();
Optional<User> optional = baseRepository.findById(body.getId());
return optional.map(user -> {
user.setAccount(body.getAccount())
.setPassword(body.getPassword())
.setStatus(body.getStatus())
.setPhoneNumber(body.getPhoneNumber())
.setEmail(body.getEmail())
.setRegisteredAt(body.getRegisteredAt())
.setUnregisteredAt(body.getUnregisteredAt());
return user;
})
.map(user -> baseRepository.save(user)) // update -> newUser
.map(updateUser -> response(updateUser)) // userApiResponse
.orElseGet(() -> Header.ERROR("데이터 없음"));
}
#Override
public Header delete(Long id) {
Optional<User> optional = baseRepository.findById(id);
// 2. repository -> delete
return optional.map(user -> {
baseRepository.delete(user);
return Header.OK();
})
.orElseGet(() -> Header.ERROR("데이터 없음"));
}
private Header<UserApiResponse> response(User user) {
UserApiResponse userApiResponse = UserApiResponse.builder()
.id(user.getId())
.account(user.getAccount())
.password(user.getPassword())
.email(user.getEmail())
.phoneNumber(user.getPhoneNumber())
.status(user.getStatus())
.registeredAt(user.getRegisteredAt())
.unregisteredAt(user.getUnregisteredAt())
.build();
return Header.OK(userApiResponse);
}
}
6.CategoryApiLogicService.java
#Service
public class CategoryApiLogicService extends
BaseService<CategoryApiRequest, CategoryApiResponse,Category> {
#Override
public Header<CategoryApiResponse> create(Header<CategoryApiRequest> request) {
CategoryApiRequest body = request.getData();
Category category = Category.builder()
.type(body.getType())
.title(body.getTitle())
.createdAt(body.getCreatedAt())
.createdBy(body.getCreatedBy())
.build();
Category newCategory = baseRepository.save(category);
return response(newCategory);
}
#Override
public Header<CategoryApiResponse> read(Long id) {
return baseRepository.findById(id)
.map(category -> response(category))
.orElseGet(()-> Header.ERROR("데이터 없음"));
}
#Override
public Header<CategoryApiResponse> update(Header<CategoryApiRequest> request) {
CategoryApiRequest body = request.getData();
return baseRepository.findById(body.getId())
.map(category -> {
category
.setType(body.getType())
.setTitle(body.getTitle())
.setCreatedAt(body.getCreatedAt())
.setCreatedBy(body.getCreatedBy());
return category;
})
.map(changeCategory -> baseRepository.save(changeCategory))
.map(newCategory -> response(newCategory))
.orElseGet(()->Header.ERROR("데이터 없음"));
}
#Override
public Header delete(Long id) {
return baseRepository.findById(id)
.map(category -> {
baseRepository.delete(category);
return Header.OK();
})
.orElseGet(()->Header.ERROR("데이터 없음"));
}
private Header<CategoryApiResponse> response(Category category){
CategoryApiResponse body = CategoryApiResponse.builder()
.id(category.getId())
.type(category.getType())
.title(category.getTitle())
.createdAt(category.getCreatedAt())
.createdBy(category.getCreatedBy())
.build();
return Header.OK(body);
}
}
8.ItemApiLogicService.java
#Service
public class ItemApiLogicService extends
BaseService<ItemApiRequest,ItemApiResponse,Item> {
#Autowired
private PartnerRepository partnerRepository;
#Override
public Header<ItemApiResponse> create(Header<ItemApiRequest> request) {
ItemApiRequest body = request.getData();
Item item = Item.builder()
.status(body.getStatus())
.name(body.getName())
.title(body.getTitle())
.content(body.getContent())
.price(body.getPrice())
.brandName(body.getBrandName())
.registeredAt(LocalDateTime.now())
.partner(partnerRepository.getOne(body.getPartnerId()))
.build();
Item newItem = baseRepository.save(item);
return response(newItem);
}
#Override
public Header<ItemApiResponse> read(Long id) {
return baseRepository.findById(id)
.map(user -> response(user))
.orElseGet(
() -> Header.ERROR("데이터 없음")
);
}
#Override
public Header<ItemApiResponse> update(Header<ItemApiRequest> request) {
ItemApiRequest body = request.getData();
return baseRepository.findById(body.getId())
.map(entityItem -> {
entityItem
.setStatus(body.getStatus())
.setName(body.getName())
.setTitle(body.getTitle())
.setContent(body.getContent())
.setPrice(body.getPrice())
.setBrandName(body.getBrandName())
.setRegisteredAt(body.getRegisteredAt())
.setUnregisteredAt(body.getUnregisteredAt());
return entityItem;
})
.map(newEntityItem -> baseRepository.save(newEntityItem))
.map(item -> response(item))
.orElseGet(() -> Header.ERROR("데이터 없음"));
}
#Override
public Header delete(Long id) {
return baseRepository.findById(id)
.map(item -> {
baseRepository.delete(item);
return Header.OK();
})
.orElseGet(() -> Header.ERROR("데이터없음"));
}
private Header<ItemApiResponse> response(Item item) {
ItemApiResponse body = ItemApiResponse.builder()
.id(item.getId())
.status(item.getStatus())
.name(item.getName())
.title(item.getTitle())
.content(item.getContent())
.price(item.getPrice())
.brandName(item.getBrandName())
.registeredAt(item.getRegisteredAt())
.unregisteredAt(item.getUnregisteredAt())
.partnerId(item.getPartner().getId())
.build();
return Header.OK(body);
}
}
and here is my error message
ERROR 4516 --- [ main] o.s.b.d.LoggingFailureAnalysisReporter :
Field entityBaseService in com.example.admin.controller.CrudController required a single bean, but 6 were found:
- categoryApiLogicService: defined in file [D:\~\service\CategoryApiLogicService.class]
- itemApiLogicService: defined in file [D:\~\service\ItemApiLogicService.class]
- orderDetailApiLogicService: defined in file [D:\~\service\OrderDetailApiLogicService.class]
- orderGroupApiLogicService: defined in file [D:\~\service\OrderGroupApiLogicService.class]
- partnerApiLogicService: defined in file [D:\~\service\PartnerApiLogicService.class]
- userApiLogicService: defined in file [D:\~\service\UserApiLogicService.class]
Thanks for reading the long story.
I hope you catch the error.
Maybe has 6 classes implement BaseService and you decide the abstract class with name entityBaseService, so Spring cannot bind exactly the bean you want.
You can use #primary mask on a class for default bean or using #qualifier to bind with the bean name
prefer: http://zetcode.com/springboot/qualifier

How to properly implement adding a new record into Elasticsearch using Spring Boot?

Getting started with Spring Boot / Spring Data / Elasticsearch application.
ES -> 6.1
Have a simple repository:
public interface BusinessMetadataRepository extends ElasticsearchRepository<BusinessMetadata, Long> {
List<BusinessMetadata> findByName(String name);
List<BusinessMetadata> findById(Long id);
}
And a Business Object:
import org.springframework.data.elasticsearch.annotations.Document;
#Document(indexName = "bsn", type = "mtd", shards = 1)
public class BusinessMetadata {
private Long id;
private String name;
public Long getId() {return id;}
public void setId(Long id) {this.id = id;}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public BusinessMetadata(Long id, String name) {
this.id = id;
this.name = name;
}
public BusinessMetadata() {
}
}
Elastic Configuration:
#Configuration
#EnableElasticsearchRepositories(basePackages = "com.discover.harmony.elastic.repository")
public class ElasticConfiguration {
#Bean
public NodeBuilder nodeBuilder() {
return new NodeBuilder();
}
#Bean
public ElasticsearchOperations elasticsearchTemplate() throws IOException {
File tmpDir = File.createTempFile("elastic", Long.toString(System.nanoTime()));
System.out.println("Temp directory: " + tmpDir.getAbsolutePath());
Settings.Builder elasticsearchSettings =
Settings.settingsBuilder()
.put("http.enabled", "true") // 1
.put("index.number_of_shards", "1")
.put("path.data", new File(tmpDir, "data").getAbsolutePath()) // 2
.put("path.logs", new File(tmpDir, "logs").getAbsolutePath()) // 2
.put("path.work", new File(tmpDir, "work").getAbsolutePath()) // 2
.put("path.home", tmpDir); // 3
return new ElasticsearchTemplate(nodeBuilder()
.local(true)
.settings(elasticsearchSettings.build())
.node()
.client());
}
}
My Rest Controller for doing search works fine:
#RestController
#RequestMapping("/rest/search")
public class SearchResource {
#Autowired
BusinessMetadataRepository businessMetadataRepository;
#GetMapping(value = "/name/{text}")
public List<BusinessMetadata> searchName(#PathVariable final String text) {
return businessMetadataRepository.findByName(text);
}
#GetMapping(value = "/all")
public List<BusinessMetadata> searchAll() {
List<BusinessMetadata> businessMetadataList = new ArrayList<>();
Iterable<BusinessMetadata> businessMetadata = businessMetadataRepository.findAll();
businessMetadata.forEach(businessMetadataList::add);
return businessMetadataList;
}
}
My Rest Controller for doing save:
#RestController
#RequestMapping("/rest/save")
public class SaveResource {
#Autowired
BusinessMetadataRepository businessMetadataRepository;
#GetMapping(value = "/name/{text}")
public void Save(String text) {
businessMetadataRepository.save(new BusinessMetadata((long)99, text));
}
}
When I test the save using Postman, I get this error:
{
"timestamp": 1514325625996,
"status": 405,
"error": "Method Not Allowed",
"exception": "org.springframework.web.HttpRequestMethodNotSupportedException",
"message": "Request method 'POST' not supported",
"path": "/rest/save/name/new-1"
}
What changes do I need to make in order to properly configure this project to support inserting new documents?
Based on the comment from AntJavaDev, I have modified my controller in the following way:
#RestController
#RequestMapping("/rest/save")
public class SaveResource {
#Autowired
BusinessMetadataRepository businessMetadataRepository;
#PostMapping("/name/{text}")
public void Save(#PathVariable String text) {
BusinessMetadata mtd = businessMetadataRepository.save(new BusinessMetadata(text));
}
}
The 2 key changes are: replace #GetMapping with #PostMapping, and include #PathVariable as a parameter qualifier.
Now it works as expected

Axon Event Handler not Working

I am developing a small cqrs implementation and I am very new to it.
I want to segregate each handlers(Command and Event) from aggregate and
make sure all are working well. The command handler are getting triggered
from controller but from there event handlers are not triggered. Could
anyone Please help on this.
public class User extends AbstractAnnotatedAggregateRoot<String> {
/**
*
*/
private static final long serialVersionUID = 1L;
#AggregateIdentifier
private String userId;
private String userName;
private String age;
public User() {
}
public User(String userid) {
this.userId=userid;
}
#Override
public String getIdentifier() {
return this.userId;
}
public void createuserEvent(UserCommand command){
apply(new UserEvent(command.getUserId()));
}
#EventSourcingHandler
public void applyAccountCreation(UserEvent event) {
this.userId = event.getUserId();
}
}
public class UserCommand {
private final String userId;
public UserCommand(String userid) {
this.userId = userid;
}
public String getUserId() {
return userId;
}
}
#Component
public class UserCommandHandler {
#CommandHandler
public void userCreateCommand(UserCommand command) {
User user = new User(command.getUserId());
user.createuserEvent(command);
}
}
public class UserEvent {
private final String userId;
public UserEvent(String userid) {
this.userId = userid;
}
public String getUserId() {
return userId;
}
}
#Component
public class UserEventHandler {
#EventHandler
public void createUser(UserEvent userEvent) {
System.out.println("Event triggered");
}
}
#Configuration
#AnnotationDriven
public class AppConfiguration {
#Bean
public SimpleCommandBus commandBus() {
SimpleCommandBus simpleCommandBus = new SimpleCommandBus();
return simpleCommandBus;
}
#Bean
public Cluster normalCluster() {
SimpleCluster simpleCluster = new SimpleCluster("simpleCluster");
return simpleCluster;
}
#Bean
public ClusterSelector clusterSelector() {
Map<String, Cluster> clusterMap = new HashMap<>();
clusterMap.put("com.user.event.handler", normalCluster());
//clusterMap.put("exploringaxon.replay", replayCluster());
return new ClassNamePrefixClusterSelector(clusterMap);
}
#Bean
public EventBus clusteringEventBus() {
ClusteringEventBus clusteringEventBus = new ClusteringEventBus(clusterSelector(), terminal());
return clusteringEventBus;
}
#Bean
public EventBusTerminal terminal() {
return new EventBusTerminal() {
#Override
public void publish(EventMessage... events) {
normalCluster().publish(events);
}
#Override
public void onClusterCreated(Cluster cluster) {
}
};
}
#Bean
public DefaultCommandGateway commandGateway() {
return new DefaultCommandGateway(commandBus());
}
#Bean
public Repository<User> eventSourcingRepository() {
EventStore eventStore = new FileSystemEventStore(new SimpleEventFileResolver(new File("D://sevents.txt")));
EventSourcingRepository eventSourcingRepository = new EventSourcingRepository(User.class, eventStore);
eventSourcingRepository.setEventBus(clusteringEventBus());
AnnotationEventListenerAdapter.subscribe(new UserEventHandler(), clusteringEventBus());
return eventSourcingRepository;
}
}
As far as I can tell, the only thing missing is that you aren't adding the User Aggregate to a Repository. By adding it to the Repository, the User is persisted (either by storing the generated events, in the case of Event Sourcing, or its state otherwise) and all Events generated by the Command Handler (including the Aggregate) are published to the Event Bus.
Note that the Aggregate's #EventSourcingHandlers are invoked immediately, but any external #EventHandlers are only invoked after the command handler has been executed.

Resources