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();
}
}
}
Related
I'm getting the below during the server startup. I'm trying to apply constructor injection here but it doesn't see to work? I do not want to assign any default value to gameId. The purpose of the class is to act as a model How can I fix this?
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'
Code
#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);
}
}
I have the following converter:
#Component
public class CountryEnumConverter implements Converter<String, CountryEnum> {
#Override
public CountryEnum convert(String country) {
CountryEnum countryEnum = CountryEnum.getBySign(country);
if (countryEnum == null) {
throw new IllegalArgumentException(country + " - Country is not supported!");
}
return countryEnum;
}
}
Registered it is invoked when used for RequestParam
#GetMapping(value = RestApiEndpoints.RESULTS, produces = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<ResultDto> getResults(
Principal principal,
#RequestParam CountryEnum country) {
....
}
But this converter is never invoked when used for field in the RequstBody:
#GetMapping(value = RestApiEndpoints.RESULTS, produces = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<ResultDto> getResults(
Principal principal,
#RequestBody MyBody myBody) {
....
}
public class MyBody {
#NotNull
private CountryEnum country;
public MyBody() {
}
public CountryEnum getCountry() {
return country;
}
public void setCountry(CountryEnum country) {
this.country = country;
}
}
Your existing org.springframework.core.convert.converter.Converter instance will only work with data submitted as form encoded data. With #RequestBody you are sending JSON data which will be deserialized using using the Jackson library.
You can then create an instance of com.fasterxml.jackson.databind.util.StdConverter<IN, OUT>
public class StringToCountryTypeConverter extends StdConverter<String, CountryType> {
#Override
public CountryType convert(String value) {
//convert and return
}
}
and then apply this on the target property:
public class MyBody {
#NotNull
#JsonDeserialize(converter = StringToCountryTypeConverter.class)
private CountryEnum country;
}
Given the similarity of the 2 interfaces I would expect that you could create one class to handle both scenarios:
public class StringToCountryTypeConverter extends StdConverter<String, CountryType>
implements org.springframework.core.convert.converter.Converter<String, CountryType> {
#Override
public CountryType convert(String value) {
//convert and return
}
}
I found out that if I add the following code to my CountryEnum will do the trick.
#JsonCreator
public static CountryEnum fromString(String value) {
CountryEnumConverter converter = new CountryEnumConverter();
return converter.convert(value);
}
This little project follows a basic MVC pattern, i'm using spring boot and apache derby as an embedded data base.
1) When adding a hardcoded object list inside service class, they all share the same id. Is there an explanation for this behavior ?
This shows the problem (Don't mind the 'kkk' objects, i've solved that part already)
Screen1
So this is the object account i'm working with :
#Entity
public class Account {
#Id
#Column(name="id")
#GeneratedValue(strategy=GenerationType.IDENTITY)
private long id;
private String owner;
private double budget;
private double budgetInvest;
private double budgetFonction;
public Account() {
}
public Account(String owner, double budget, double budgetInvest, double budgetFonction
) {
this.owner=owner;
this.budget = budget;
this.budgetInvest = budgetInvest;
this.budgetFonction = budgetFonction;
}
public Account (String owner, double budget) {
this.owner = owner;
this.budget=budget;
}
public Account (String owner) {
this.owner=owner;
}
public long getId() {
return id;
}
public void setId(long id) {
this.id = id;
}
public double getBudget() {
return budget;
}
public void setBudget(double budget) {
this.budget = budget;
}
public double getBudgetInvest() {
return budgetInvest;
}
public void setBudgetInvest(double budgetInvest) {
this.budgetInvest = budgetInvest;
}
public double getBudgetFonction() {
return budgetFonction;
}
public void setBudgetFonction(double budgetFonction) {
this.budgetFonction = budgetFonction;
}
public String getOwner() {
return owner;
}
public void setOwner(String owner) {
this.owner = owner;
}
}
These are the lines responsible for displaying the objects inside the view :
<tr th:each="account : ${accounts}">
<td th:text="${account.id}">id</td>
<td><a href="#" th:text="${account.owner}">Title
...</a></td>
<td th:text="${account.budget}">Text ...</td>
</tr>
Here is the controller :
#Controller
public class AccountController {
#Autowired
private AccountService accountService;
#RequestMapping(value="/", method=RequestMethod.GET)
public String index() {
return "index";
}
#RequestMapping(value="/accountAdd", method=RequestMethod.GET)
public String addAccount(Model model) {
model.addAttribute("account", new Account());
return "accountAdd";
}
#RequestMapping(value="/accountAdd", method=RequestMethod.POST)
public String postAccount(#ModelAttribute Account account) {
accountService.addAccount(account);
return "redirect:listAccount";
}
#RequestMapping(value="/listAccount", method=RequestMethod.GET)
public String listAccount(Model model) {
System.out.println(accountService.getAllAccounts());
model.addAttribute("accounts",accountService.getAllAccounts());
return "listAccount";
}
}
And finally the service class :
#Service
public class AccountService {
#Autowired
private AccountRepository accountRepository;
public List<Account> getAllAccounts(){
List<Account>accounts = new ArrayList<>(Arrays.asList(
new Account("Maths Department",1000000,400000,600000),
new Account("Physics Department",7000000,200000,500000),
new Account("Science Department",3000000,700000,1000000)
));
accountRepository.findAll().forEach(accounts::add);
return accounts;
}
public Account getAccount(long id) {
return accountRepository.findById(id).orElse(null);
}
public void addAccount(Account account) {
accountRepository.save(account);
}
public void updateAccount(long id, Account account) {
accountRepository.save(account);
}
public void deleteAccount(long id) {
accountRepository.deleteById(id);
}
}
Ok, so while i haven't yet found the exact answer as to why it affects the same id for every object in a static list.
I found an elegant workaround to not only solve the issue but also enhance the structure of the code.
Instead of doing whatever barbaric initialization I was trying to perform, It's way better to do this inside the main class :
#SpringBootApplication
public class PayfeeApplication {
#Autowired
private AccountRepository accountRepository;
public static void main(String[] args) {
SpringApplication.run(PayfeeApplication.class, args);
}
#Bean
InitializingBean sendDatabase() {
return () -> {
accountRepository.save(new Account("Maths Department",1000000,400000,600000));
accountRepository.save(new Account("Physics Department",7000000,200000,500000));
accountRepository.save(new Account("Science Department",3000000,700000,1000000));
};
}
}
I want to create a MongoDB collection for each month dynamically.
Example: viewLog_01_2018, viewLog_02_2018
#Document(collection = "#{viewLogRepositoryImpl.getCollectionName()}")
#CompoundIndexes({
#CompoundIndex(def = "{'viewer':1, 'viewed':1}", name = "viewerViewedIndex",unique=true)
})
public class ViewLog {
private Integer viewer;
private Integer viewed;
private Date time;
public Integer getViewer() {
return viewer;
}
public void setViewer(Integer viewer) {
this.viewer = viewer;
}
public Integer getViewed() {
return viewed;
}
public void setViewed(Integer viewed) {
this.viewed = viewed;
}
public Date getTime() {
return time;
}
public void setTime(Date time) {
this.time = time;
}
}
The implementation for the collection name is as follows:
#Repository
public class ViewLogRepositoryImpl implements ViewLogRepositoryCustom {
private String collectionName;
public ViewLogRepositoryImpl() {
CommonUtility common = new CommonUtility();
Pair<Integer, Integer> pair = common.getStartingEndingDateOfMonth();
setCollectionName("viewLog_"+pair.getFirst()+"_"+pair.getSecond());
}
#Override
public String getCollectionName() {
return collectionName;
}
#Override
public void setCollectionName(String collectionName) {
this.collectionName = collectionName;
}
}
On my each request, to save a document, I am setting the collection name as:
#Autowired
ViewLogRepository viewLogRepository;
public boolean createLog(int viewer, int viewed,String viewed_mmm, Date time){
CommonUtility common = new CommonUtility();
Pair<Integer, Integer> pair = common.getStartingEndingDateOfMonth();
viewLogRepository.setCollectionName("viewLog_"+pair.getFirst()+"_"+pair.getSecond());
ViewLog viewLog = new ViewLog();
viewLog.setViewer(viewer);
viewLog.setViewed(viewed);
viewLog.setTime(time);
ViewLog viewLog2 = viewLogRepository.save(viewLog);
return true;
}
The problem I am facing is that I when for the first time I up my service the mongo collection that is created has the unique attribute for the fields 'viewer' and 'viewed' but for any subsequent collection that is created dynamically, the document does not have the unique constraint and multiple entries of same viewer-viewed combination are able to be inserted.
Any help will be very much appreciated.
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.