I have a situation in my requirements I would like to see if someone has a strong opinion about it.
The project I'm working on requires that, given a Customer, once the Customer adds a Product to a Cart, if the Cart is not present, the Cart has to be created. At the moment, the aggregate is the Customer and it contains the Cart that contains the Product. Because this system is kind of a side one that supports the real e-commerce project, the creation of the Customer and the Cart is taken for granted once an "AddProductCommand" is received. In that situation, both Customer and Cart have to be created if they are not there already.
My current implementation creates the Customer at the application service if it doesn't exist. Once created, I pass the AddProductRequest using customer.addProduct(addProductRequest). AddProductRequest contains the Cart id and the Product id. The problem comes here. Once in the Customer aggregate, if the Cart is not there, I have to create it. So basically, I don't have an addCart in customer with a Cart entity that I call first to then call addProduct. I need to create the Cart if it's not there from inside the Customer aggregate and then add the Product to it. And to do this, I don't create any factory as I don't want to have a static method that complicates testing and I can't just create a new factory inside the aggregate either. I just create the Cart entity using the new operator in a protected method that I override when testing to verify what I'm doing there.
So my question is, is this a valid approach? In my head, to add a Product to a Cart, the Cart should be added first to the Customer and fail if it's not there. To do it this way I would need to add logic to the application service where first I would ask the Customer if he has a Cart with that id, create it otherwise, and add it to Customer before adding the Product. I could add a domain service that is given this request but then I would need to inject the factory to create the Cart into it, when I've read in several places that a domain service shouldn't be injected any factory, that should be the work of the application service.
I could do it that way but the project will get more complicated in the future as there will be yet another layer where a Product could be added a list of Vouchers having to also create Customer, Cart and Product if they are not there when the AddVoucherCommand is consumed. In this situation, if I don't want to create the entities inside the model, I would need to check at the application/domain service whether each aggregate or entity has the necessary entity inside it, which I don't think is very DDD friendly, or just keep doing what I'm doing at the moment. This is, each aggregate/entity is in charge of create the necessary entity before calling the addXXXX method on it.
Some simplified code to explain what I'm doing at the moment and what I'm going to have to do in the future:
public class CustomerService {
public void addVoucher(AddVoucherRequest addVoucherRequest) {
Customer customer = customerRepo.load(customerId);
customer.addVoucher(addVoucherRequest);
customerRepo.save(customer);
}
}
public class Customer() {
public void addVoucher(AddVoucherRequest addVoucherRequest) {
Cart cart = getOrCreateIfAbsent(addVoucherRequest.getCartId());
cart.addVoucher(addProductRequest);
}
private Cart getOrCreateIfAbsent(long cartId){
Optional<Cart> cart = carts.stream().filter(cart -> cart.getId() == cartId).findFirst();
return cart.orElseGet(() -> {
Cart newCart = createCart(cartId);
carts.add(newCart);
return newCart;
}
}
protected Cart createCart(long cartId) {
return new Cart(cartId);
}
}
public class Cart() {
public void addVoucher(AddVoucherRequest addVoucherRequest) {
Product product = getOrCreateIfAbsent(addVoucherRequest.getProductId());
product.addVoucher(addVoucherRequest);
}
private void getOrCreateIfAbsent(long productId) {
Optional<Product> product = products.stream().filter(product -> product.getId() == productId).findFirst();
return product.orElseGet(() -> {
Product newProduct = createProduct(productId);
products.add(newProduct );
return newProduct ;
}
}
protected Product createProduct(long productId) {
return new Product(productId);
}
}
Any suggestions?
Thanks!
Choosing to wait for a Customer to order something to create him/her in the new system rather than importing all Customers from the legacy system upfront is a purely technical decision, it has nothing to do with your domain IMO.
Therefore, it shouldn't affect the way you design your domain objects in any way - as I understand it, getOrCreateIfAbsent() should be a behavior in some kind of application service, not in one of your entities.
Related
Update: The issue seems to be the id that I'm using twice, or in other words, the id from the product entity that I want to use for the productinventory entity. As soon as I generate a new id for the productinventory entity, it seems to work fine. But I want to have the same id for both, since they're the same product.
I have 2 Services:
ProductManagementService (saves a Product entity with product details)
1.) For saving the Product Entity, I implemented an EventHandler that listens to ProductCreatedEvent and saves the product to a mysql database.
ProductInventoryService (saves a ProductInventory entity with stock quantities of product to a certain productId defined in ProductManagementService )
2.) For saving the ProductInventory Entity, I also implemented an EventHandler that listens to ProductInventoryCreatedEvent and saves the product to a mysql database.
What I want to do:
When a new Product is created in ProductManagementService, I want to create a ProductInventory entity in ProductInventoryService directly afterwards and save it to my msql table. The new ProductInventory entity shall have the same id as the Product entity.
For that to accomplish, I created a Saga, which listes to a ProductCreatedEvent and sends a new CreateProductInventoryCommand. As soon as the CreateProductInventoryCommand triggers a ProductInventoryCreatedEvent, the EventHandler as described in 2.) should catch it. Except it doesn't.
The only thing thta gets saved is the Product Entity, so in summary:
1.) works, 2.) doesn't. A ProductInventory Aggregate does get created, but it doesn't get saved since the saving process that is connected to an EventHandler isn't triggered.
I also get an Exception, the application doesn't crash though: Command 'com.myApplication.apicore.command.CreateProductInventoryCommand' resulted in org.axonframework.commandhandling.CommandExecutionException(OUT_OF_RANGE: [AXONIQ-2000] Invalid sequence number 0 for aggregate 3cd71e21-3720-403b-9182-130d61760117, expected 1)
My Saga:
#Saga
#ProcessingGroup("ProductCreationSaga")
public class ProductCreationSaga {
#Autowired
private transient CommandGateway commandGateway;
#StartSaga
#SagaEventHandler(associationProperty = "productId")
public void handle(ProductCreatedEvent event) {
System.out.println("ProductCreationSaga, SagaEventHandler, ProductCreatedEvent");
String productInventoryId = event.productId;
SagaLifecycle.associateWith("productInventoryId", productInventoryId);
//takes ID from product entity and sets all 3 stock attributes to zero
commandGateway.send(new CreateProductInventoryCommand(productInventoryId, 0, 0, 0));
}
#SagaEventHandler(associationProperty = "productInventoryId")
public void handle(ProductInventoryCreatedEvent event) {
System.out.println("ProductCreationSaga, SagaEventHandler, ProductInventoryCreatedEvent");
SagaLifecycle.end();
}
}
The EventHandler that works as intended and saves a Product Entity:
#Component
public class ProductPersistenceService {
#Autowired
private ProductEntityRepository productRepository;
//works as intended
#EventHandler
void on(ProductCreatedEvent event) {
System.out.println("ProductPersistenceService, EventHandler, ProductCreatedEvent");
ProductEntity entity = new ProductEntity(event.productId, event.productName, event.productDescription, event.productPrice);
productRepository.save(entity);
}
#EventHandler
void on(ProductNameChangedEvent event) {
System.out.println("ProductPersistenceService, EventHandler, ProductNameChangedEvent");
ProductEntity existingEntity = productRepository.findById(event.productId).get();
ProductEntity entity = new ProductEntity(event.productId, event.productName, existingEntity.getProductDescription(), existingEntity.getProductPrice());
productRepository.save(entity);
}
}
The EventHandler that should save a ProductInventory Entity, but doesn't:
#Component
public class ProductInventoryPersistenceService {
#Autowired
private ProductInventoryEntityRepository productInventoryRepository;
//doesn't work
#EventHandler
void on(ProductInventoryCreatedEvent event) {
System.out.println("ProductInventoryPersistenceService, EventHandler, ProductInventoryCreatedEvent");
ProductInventoryEntity entity = new ProductInventoryEntity(event.productInventoryId, event.physicalStock, event.reservedStock, event.availableStock);
System.out.println(entity.toString());
productInventoryRepository.save(entity);
}
}
Product-Aggregate:
#Aggregate
public class Product {
#AggregateIdentifier
private String productId;
private String productName;
private String productDescription;
private double productPrice;
public Product() {
}
#CommandHandler
public Product(CreateProductCommand command) {
System.out.println("Product, CommandHandler, CreateProductCommand");
AggregateLifecycle.apply(new ProductCreatedEvent(command.productId, command.productName, command.productDescription, command.productPrice));
}
#EventSourcingHandler
protected void on(ProductCreatedEvent event) {
System.out.println("Product, EventSourcingHandler, ProductCreatedEvent");
this.productId = event.productId;
this.productName = event.productName;
this.productDescription = event.productDescription;
this.productPrice = event.productPrice;
}
}
ProductInventory-Aggregate:
#Aggregate
public class ProductInventory {
#AggregateIdentifier
private String productInventoryId;
private int physicalStock;
private int reservedStock;
private int availableStock;
public ProductInventory() {
}
#CommandHandler
public ProductInventory(CreateProductInventoryCommand command) {
System.out.println("ProductInventory, CommandHandler, CreateProductInventoryCommand");
AggregateLifecycle.apply(new ProductInventoryCreatedEvent(command.productInventoryId, command.physicalStock, command.reservedStock, command.availableStock));
}
#EventSourcingHandler
protected void on(ProductInventoryCreatedEvent event) {
System.out.println("ProductInventory, EventSourcingHandler, ProductInventoryCreatedEvent");
this.productInventoryId = event.productInventoryId;
this.physicalStock = event.physicalStock;
this.reservedStock = event.reservedStock;
this.availableStock = event.availableStock;
}
}
What you are noticing right now is the uniqueness requirement of the [aggregate identifier, sequence number] pair within a given Event Store. This requirement is in place to safe guard you from potential concurrent access on the same aggregate instance, as several events for the same aggregate all need to have a unique overall sequence number. This number is furthermore use to identify the order in which events need to be handled to guarantee the Aggregate is recreated in the same order consistently.
So, you might think this would opt for a "sorry there is no solution in place", but that is luckily not the case. There are roughly three things you can do in this set up:
Life with the fact both aggregates will have unique identifiers.
Use distinct bounded contexts between both applications.
Change the way aggregate identifiers are written.
Option 1 is arguably the most pragmatic and used by the majority. You have however noted the reuse of the identifier is necessary, so I am assuming you have already disregarded this as an option entirely. Regardless, I would try to revisit this approach as using UUIDs per default for each new entity you create can safe you from trouble in the future.
Option 2 would reflect itself with the Bounded Context notion pulled in by DDD. Letting the Product aggregate and ProductInventory aggregate reside in distinct contexts will mean you will have distinct event stores for both. Thus, the uniqueness constraint would be kept, as no single store is containing both aggregate event streams. Whether this approach is feasible however depends on whether both aggregates actually belong to the same context yes/no. If this is the case, you could for example use Axon Server's multi-context support to create two distinct applications.
Option 3 requires a little bit of insight in what Axon does. When it stores an event, it will invoke the toString() method on the #AggregateIdentifier annotated field within the Aggregate. As your #AggregateIdentifier annotated field is a String, you are given the identifier as is. What you could do is have typed identifiers, for which the toString() method doesn't return only the identifier, but it appends the aggregate type to it. Doing so will make the stored aggregateIdentifier unique, whereas from the usage perspective it still seems like you are reusing the identifier.
Which of the three options suits your solution better is hard to deduce from my perspective. What I did do, is order them in most reasonable from my perspective.
Hoping this will help your further #Jan!
I have some design/implementation issue that I just can't wrap my head around it. I am currently working on a text-based game with multiple players. I kind of understand how it works for Player-to-Server, I meant that Server sees every individual Player as the same.
I'm using spring-boot 2, spring-web, thymeleaf, hibernate.
I implemented a custom UserDetails that returns after the user login.
#Entity
#Table(name = "USER")
public class User implements Serializable {
#Id
private long userId;
#Column(unique = true, nullable = false)
private String userName;
#OneToOne(cascade = CascadeType.ALL)
#JoinColumn(name = "playerStatsId")
private PlayerStats stats;
}
public class CurrentUserDetailsService implements UserDetailsService {
#Override
public CurrentUser loadUserByUsername(String userName) {
User user = this.accountRepository.findByUserName(userName)
.orElseThrow(() ->
new UsernameNotFoundException("User details not found with the provided username: " + userName));
return new CurrentUser(user);
}
}
public class CurrentUser implements UserDetails {
private static final long serialVersionUID = 1L;
private User user = new User();
public CurrentUser(User user) {
this.user = user;
}
public PlayerStats getPlayerStats() {
return this.user.getStats();
}
// removed the rest for brevity
}
Hence, in my controller, I can do this to get the CurrentUser.
*Note each User is also a player.
#GetMapping("/attackpage")
public String viewAttackPage(#AuthenticationPrincipal CurrentUser currentUser) {
// return the page view for list of attacks
return "someview";
}
The currentUser here would reflect to the current user per say (Player 1 or 2 or 3 and so on). Which works fine for most of the stuff happening to themselves such as purchasing some stuff, updating profile and so on.
But what I can't get or know how to achieve is when 2 players interact.
For example, Player 1 attacks Player 2. If I am Player 1, what I'll do is to click the "Attack" on the View and select the Player 2, and submit the command. Hence, in the controller, it will be something like this.
#GetMapping("/attack")
public String launchAttack(#AuthenticationPrincipal CurrentUser currentUser, #RequestParam("playername") String player2) {
updatePlayerState(player2);
return "someview";
}
public void updatePlayerState(String player) {
User user = getUserByPlayername(player);
// perform some update to player state (say health, etc)
// update back to db?
}
Here's is what really got me confused.
As seen previously, when each User/Player logs in, a set of user (player) current state will be pulled from the DB and store "in-memory".
Hence, when Player 1 attacks Player 2,
How do I "notify" or update Player 2 that the stats has changed, and thus, Player 2 should pull updated stats from db to memory.
How to tackle the possible concurrency issue here? For example, Player 2 health is 50 in DB. Player 2 then perform some action (say purchase health potion + 30), which then update the DB (health to 80). However, just before the DB is updated, Player 1 has already launch the attack and grab from DB the state of Player 2 where it will return 50 since DB has yet to be updated. So now, whatever changes made in getUserByPlayername() and update to the DB will be wrong, and the entire state of the Player will be "de-sync". I hope I am making sense here.
I understand that there is #Version in hibernate for optimistic locking but I'm not sure if it's applicable in this case. And would spring-session be useful in such case?
Should I not store the any data in memory when user login? Should I always be retrieving data from DB only when some action is performed? Like when viewProfile, then I pull from accountRepository. or when viewStats then I pull from statsRepository and on so.
Do point me in the right direction. Would appreciate for any concrete example of sort, or some kind of video/articles. If there is any additional information required, do let me know and I'll try to explain my case better.
Thank you.
I think that you should not be updating the currentUser in your Controller methods, and should not be relying on the data in that object to represent a player's current state. There are probably ways to get that to work, but you'd need to mess around with updating the security context.
I also recommend that you lookup Users by id instead of userName, so will write the rest of this answer with that approach. If you insist on finding Users by userName, adjust where necessary.
So, keeping it simple, I would have a reference to the accountRepository in the Controller, and then, whenever you need to get or update a player's state, use
User user = accountRepository.findById(currentUser.getId())
Yes, #Version and optimistic locking will help with the concurrency issues that you're concerned about. You can reload the Entity from the database, and retry the operation if you catch an #OptimisticLockException. Or, you may want to respond to player 1 with something like "Player 2 has just purchased a potion of healing, and is now 80 heath, do you still want to attack?"
I'm not a spring user, but I think that the problem is more conceptual than technical.
I'll try to provide an answer which uses a general approach, while writing the examples in a JavaEE style so that they should be understandable, and hopefully, portable to spring.
First of all: every single DETACHED entity is stale data. And stale data is not "trustable".
So:
each method that modify the state of an object should re-fetch the object from DB inside the transaction:
updatePlayerState() should be a transaction-boundary method (or called inside a tx), and getUserByPlayername(player) should fetch the target object from the DB.
JPA speaking: em.merge() is forbidden (without proper locking, i.e. #Version).
if you (or spring) are doing this already, there's little to add.
WRT the "lost update problem" you mention in your 2. be aware that this covers the application server side (JPA/Hibernate), but the very same problem could be present on DB side, which should be properly configured for, at least, repeatable read isolation. Take a look at MySQL does not conform to Repeatable Read really, if you are using it.
you have to handle controller fields that refer stale Players/Users/Objects. You have, at least, two options.
re-fetch for each request: suppose Player1 has attacked Player2 and diminished Player2 HP by 30. When Player2 goes to a view that shows his HP, the controller behind that view should have re-fetched the Player2/User2 entity before rendering the view.
In other words, all of your presentation (detached) entities should be, sort of, request-scoped.
i.e you can use a #WebListener to reload your Player/User:
#WebListener
public class CurrentUserListener implements ServletRequestListener {
#Override
public void requestInitialized(ServletRequestEvent sre) {
CurrentUser currentUser = getCurrentUser();
currentUser.reload();
}
#Override
public void requestDestroyed(ServletRequestEvent sre) {
// nothing to do
}
public CurrentUser getCurrentUser() {
// return the CurrentUser
}
}
or a request-scoped bean (or whatever-spring-equivalent):
#RequestScoped
public class RefresherBean {
#Inject
private CurrentUser currentUser;
#PostConstruct
public void init()
{
currentUser.reload();
}
}
notify other controller instances: if the update succeeded a notification should be sent to other controllers.
i.e. using CDI #Observe (if you have CDI available):
public class CurrentUser implements UserDetails {
private static final long serialVersionUID = 1L;
private User user = new User();
public CurrentUser(User user) {
this.user = user;
}
public PlayerStats getPlayerStats() {
return this.user.getStats();
}
public void onUpdate(#Observes(during = TransactionPhase.AFTER_SUCCESS) User user) {
if(this.user.getId() == user.getId()) {
this.user = user;
}
}
// removed the rest for brevity
}
Note that CurrentUser should be a server-managed object.
I have an entity type with several fields. Some of them are references to other entities. I want to create a Rest API endpoint in Spring that lets users to update entities of this type.
Assume that I have an entity User that contains a list of friends. I only want to let users update some specific fields, such as name, age and description, of a user entity, and not list of friends.
Besides, I only want those attribute of the entity to be updated that the corresponding passed in values are not null.
public class UserController {
#RequestMapping(path="",method=RequestMethod.PUT)
public void update(#RequestBody User user) {
userService.save(user);
}
}
How can I make this possible ?
Instead of taking User object as a parameter in your update() method you can take a DTO class that only defines the properties you need to change.
Define a class UpdateUserDTO like so
public class UpdateUserDTO {
private String name;
private String description;
//other fields you want the clients to change.
...
}
Now this UpdateUserDTO can be used as a data transfer object in your update method like so.
public class UserController {
#RequestMapping(path="",method=RequestMethod.PUT)
public void update(#RequestBody UpdateUserDTO dto) {
//validate your dto properties and then update your user entity.
userService.save(user);
}
}
Session session = sessionFactory.getCurrentSession();
String newuserId=newuser.getUserId();
session.clear();
User user=userService.readUser(newuserId);
Property1 prop1=user.getProperty1();
List<Property2> prop2=user.getProperty2();
session.clear();
newuser.setProp1(prop1);
newuser.setProp2(prop2);
newuser.save(); //You should go through services and abstraction layers before saving aka it should be in the DAO implementation layer.
Your question needs improvement but try this answer.
You basically save the missing properties in some variables and you append them to your front end User object.
Based on the HQL output it seems like this way gets the user and adds the other properties to it.
I have an issue i'm struggling with for some time now. Im trying to implement a news feed feature in my app using GAE cloud endpoints and java. The common concept is of followers and followees, where an action of a followee can be seen by his followers. A new follower should also see his followees past actions, not only from the time he started following.
I made a few tries with the following components. Each try worked great but was lacking something:
On each user action i added a 'log' entity into the datastore with the user id included. When a user was displaying his news feed i just queried for all those entities by their user ids according to the user's followees list. Everything was fine until i realized that a 'IN' query cannot be cursored. So this option was gone.
On this try, which is also the current state of the application, im using the Search API. Upon every user action im not storing a 'log' entity into the datastore anymore but a document into a search index. Complex queries can be cursored here and the world is smiling again. But... im not too sure that, billing wise, this is a smart descision. It seems that the costs of searching/adding/deleting documents along side the documented daily limitations is making the whole thing a bit too sketchy.
The next try should be Prospective Search API. From what i'm reading in the documents it seems the right component to pick for that purpose. Unfortunately, the documentation is really poor and give very little examples. Also the billing information is unclear.
So im asking for the advice of the stackoverflow community. Can you please advise me about this matter ? and if Prospective Search is the right option to choose, can you please provide some clear sample java code that uses cloud endpoints?
EDIT : Just to emphasize the main design requirement here - The news feed feature need to have the ability to fetch sorted followees actions using a cursor (in order avoid querying the whole batch).
Use a pull-aggregate-per-follower model: periodically (or on demand) query all followees actions once and then cache them inside a dedicated per-follower entity. Remember the time of last query, so next time you just query from that point on (assuming actions can not be added/changed to the past times).
This will give you the following features (and limitations):
If query is on-demand, than you will not need to query for users that are inactive.
Since the query is "new-only" (looks for new actions only), it would cost you nothing if it returned zero results.
You will only query each followee actions per follower once. After that all recent actions would be cached inside one entity and loaded into memory with one get. This should be a substantial cost and time saving.
You could sort/filter actions in memory any way you wish.
Limitations:
Entities have a 1MB limit, so there is a max no of actions that you can cache in one entity. So you will either need to limit caching of recent actions per user or spread out action caching over multiple entities.
You will need to use IN query over followees (max 30) and also use parallel threads to achieve decent performance. This could easily hit 3-5 seconds when querying over 1000-2000 followees. Also, you could easily hit RPC limit (aka max concurrent API calls) per instance when serving multiple users at the same time.
I hope I understand the question correctly - you want to implement a news feed into your application and allow users to follow each other. The new followers need to be able to see the users actions. I am sure there are multiple other ways of solving this problem, but I will attempt to help you out by providing a solution that makes use of JAVA JDO to access the datastore.
I would first design the entity relationships in JDO as follows:
1 User to many actions.
1 User to many followers (User).
1 User to many following (User).
Here are simple JDO classes:
User Class:
#PersistenceCapable(identityType=IdentityType.APPLICATION)
public class User {
#PrimaryKey
#Persistent(valueStrategy=IdGeneratorStrategy.IDENTITY)
private Key key;
#Persistent
private String userId; // Google unique user ID, could also store user email.
#Persistent
private Set<Key> actions;
#Persistent
private Set<Key> followers;
#Persistent
private List<Key> following;
public User(Key key, String userId) {
this.key = key;
this.userId = userId;
this.actions = new HashSet<Key>();
this.followers = new HashSet<Key>();
this.following = new HashSet<Key>();
}
public Key getKey() {
return this.key;
}
public void addAction(Key actionKey) {
this.actions.add(actionKey);
}
public void addActions(Set<Key> actionKeys) {
this.actions.addAll(actionKeys);
}
public Set<Key> getActions() {
return this.actions;
}
public void addFollower(Key followerKey) {
this.followers.add(followerKey);
}
public void addFollowers(Set<Key> followerKeys) {
this.followers.addAll(followerKeys);
}
public Set<Key> getFollowers() {
return this.followers;
}
public void addFollowing(Key followingKey) {
this.following.add(followingKey);
}
public void addAllFollowing(Set<Key> followingKeys) {
this.following.addAll(followingKeys);
}
public Set<Key> getFollowing() {
return this.following;
}
}
Action Class:
#PersistenceCapable(identityType=IdentityType.APPLICATION)
public class Action {
#PrimaryKey
#Persistent(valueStrategy=IdGeneratorStrategy.IDENTITY)
private Key key;
#Persistent
Date date;
#Persistent
private String title;
public Action(Key key, String title) {
this.key = key;
this.title = title;
this.date = new Date(); // date of creation (now).
}
public Key getKey() {
return this.key;
}
public void setTitle(String title) {
this.title = title;
}
public String getTitle() {
return this.title;
}
}
The Action class makes use of a Date property, you can refer to the documentation for applicable data types in the datastore. When an action is created, a Date object is allocated and initialized so that it represents the time at which it was allocated, measured to the nearest millisecond.
In my example above I linked the entities by their Keys, you could instead link them by their classes as follows:
List<Action> actions;
The relationship in my example is one of an unowned one-to-many relationship, perhaps it should be owned one-to-many. More information here for your to take a look and perhaps decide which would be best for your solution.
Once the relationships have been defined, you can create your endpoint classes around the JDO model classes. This will create basic api methods. You might want to change the endpoint class methods to suit your needs, for example change the way an action is created. A basic example would be to create the key from the actions title as follows (ActionEnpoint.java):
...
#ApiMethod(name = "insertAction")
public Action insertAction( #Named("title") String title ) {
PersistenceManager pm = getPersistenceManager();
Key key = KeyFactory.createKey(Action.class.getSimpleName(), title);
Action action = null;
try {
action = new Action(key, title);
pm.makePersistent(action);
} finally {
pm.close();
}
return action;
}
...
If you want to, you can add a method to your UserEndpoint class to query the datastore and return all actions belonging to that user and per date using the datastore query objects.
You need to add a method to your UserEndpoint class that allows you to add an action to that user, here is a simple example:
...
#ApiMethod(name = "addActionToUser")
public Achiever addActionToUser(
#Named("userId") String userId,
#Named("actionTitle") String actionTitle) {
PersistenceManager pm = getPersistenceManager();
Key userKey = KeyFactory.createKey(User.class.getSimpleName(), userId);
Key actionKey = KeyFactory.createKey(Action.class.getSimpleName(), actionTitle);
User user = null;
try {
user = (User) pm.getObjectById(User.class, userKey);
user.addAction(actionKey);
pm.makePersistent(user);
} catch (Exception e) {
}
return user;
}
...
Once all of the above is complete you can easily get the list of actions per user by calling the getUser method in your UserEndpoint class, which returns a User object. You can then call [ReturnedUserObject].getActions(). A new follower can now view all of the "followees" actions by just calling the api method to get that "followees" object and get his/her actions. You can then just sort the actions by date or however you envision it.
I hope I understood your question correctly, I was unsure about the first component you mentioned, but it seemed as though you got your relationships mixed up. I hope this solution points you in the right direction at least :).
If you need any additional help or clarification, or my answer was completely off point to what you were looking for then please let me know.
Kind regards,
Miki
i've hit a block once again with hibernate.I've posted numerous times on different aspects of the user and contact management that i've been building.
The sad thing is that i didn't really have the time to play with it and understand it better before actually starting working with it. Sorry but English is not my native language, i rather speak french. And again i've started coding in java in an autodidact way.i'm doing all of this by reading books and haven't gone to school for it. with time constraints it's hard to read a book from beginning to the end.
I'm not sure i should put every of my codes dealing with an issue here and from what i've learned from other forum is to post just the necessary and being concise.
So in my User model i have UserAccount class, Profile that holds details like name, preferences etc , AccountSession and Phone.
my contact management model have Contact and Group.
UserAccount has one-to-one association with Profile, one-to-many with AccountSession,contact and group, all bidirectional.the one-to-many association with phone is unidirectional because contact also has and unidirectional with Phone.
Contact has a bidirectional many-o-many with group and one-to-many with phone that i said earlier.
Group also has a many-to-many bedirectional with contact.
here are the mappings
// UserAccount
......
#OneToOne(targetEntity=UserProfileImpl.class,cascade={CascadeType.ALL})
#org.hibernate.annotations.Cascade(value=org.hibernate.annotations.CascadeType.DELETE_ORPHAN)
#JoinColumn(name="USER_PROFILE_ID")
private UserProfile profile;
#OneToMany(targetEntity=ContactImpl.class, cascade={CascadeType.ALL}, mappedBy="userAccount")
#org.hibernate.annotations.Cascade(value=org.hibernate.annotations.CascadeType.DELETE_ORPHAN)
private Set<Contact> contacts = new HashSet<Contact>();
#OneToMany(targetEntity=GroupImpl.class, cascade={CascadeType.ALL}, mappedBy="userAccount")
#org.hibernate.annotations.Cascade(value=org.hibernate.annotations.CascadeType.DELETE_ORPHAN)
private Set<Group> groups = new HashSet<Group>();
.......
//Group
#ManyToOne(targetEntity=UserAccountImpl.class)
#JoinColumn(name="USER_ACCOUNT_ID",nullable=false)
private UserAccount userAccount;
#ManyToMany(targetEntity=ContactImpl.class,cascade={CascadeType.PERSIST, CascadeType.MERGE})
#JoinTable(name="GROUP_CONTACT_MAP", joinColumns={#JoinColumn(name="GROUP_ID")},
inverseJoinColumns={#JoinColumn(name="CONTACT_ID")})
private Set<Contact> contacts = new HashSet<Contact>();
//Contact
....
#ManyToOne(targetEntity=UserAccountImpl.class)
#JoinColumn(name="USER_ACCOUNT_ID",nullable=false)
private UserAccount userAccount;
#ManyToMany(targetEntity=GroupImpl.class, mappedBy="contacts")
private Set<Group> groups=new HashSet<Group>();
....
// helper methods from group
public void addContact(Contact contact) {
try{
this.getContacts().add(contact);
contact.getGroups().add(this);
}catch(Exception e) {
}
}
//helper method from group
public void removeContact(Contact contact) {
contact.getGroups().remove(contact);
this.getContacts().remove(contact);
}
//helper method from contact
public void addGroup(Group group) {
try{
this.getGroups().add(group);
group.getContacts().add(this);
} catch(Exception e) {
e.printStackTrace();
}
}
//Helper method from group
public void removeGroup(Group group){
try{
group.getContacts().remove(this);
this.getGroups().remove(group);
} catch(Exception e) {
e.printStackTrace();
}
}
//UserAccount setter from Contact.All the children with many-to-one have the same
/**
* #param userAccount the userAccount to set
*/
public void setUserAccount(UserAccount userAccount) {
this.userAccount = userAccount;
}
I'ld like to pull the UserAccount by its email field which is an unique field in the UserAccount table.
In the UserAccountDAO the method i call to get the UserAccount is getUserAccountByEmail here below.So i expect this method to load all the children collections of the UserAccount namely its Contact collection, group collection.I want it in such a way that when UserAccount is loaded with Contacts collection each of the contact object has its reference with its belonging groups collection if any etc and vice versa.
public UserAccount getUserAccountByEmail(String email) {
// try {
logger.info("inside getUserAccountByEmail");
logger.debug(email);
Session session = (Session) this.getDBSession().getSession();
UserAccount user = (UserAccount) session.createCriteria(this.getPersistentClass())
.setFetchMode("contacts", FetchMode.SELECT) //recently added
.setFetchMode("groups", FetchMode.SELECT) // recently added
.add(Restrictions.eq("email", email))
.uniqueResult();
logger.debug(user);
return user;
// } catch(NonUniqueResultException ne) {
// logger.debug("Exception Occured: getUserAccountByEmail returns more than one result ", ne);
// return null;
// } catch(HibernateException he){
// logger.debug("Exception Occured: Persistence or JDBC exception in method getUserAccountByEmail ",he);
// return null;
// }catch(Exception e) {
// logger.debug("Exception Occured: Exception in method getUserAccountByEmail", e);
// return null;
// }
Since there has to be an UserAccount before any contact and groups, in my unit test when testing the saving of a contact object for which there must be an existing group i do this in order
a create userAccount object ua.
b create group object g1;
c create contact object c1;
d ua.addGroup(g1);
e c1.setUserAccount(ua);
f c1.addGroup(g1);
g uaDao.save(ua); // which saves the group because of the cascade
h cDao.save(c1);
Most of the time i use the session.get() from hibernate to pull c1 by its it id generated by hibernate and do all the assertions which works actually.
but in Integration test when i call getUserAccountByEmail with and without the setFetchMode and it returns the right object but then all the children collections are empty. i've tried the JOIN and the SELECT.the query string changes but then the result set is still the same. So this arises some questions :
1. What should i do to fix this?
2. the helper method works fine but it's on the parent side(i do it in the test).What i've been wondering about is that doing c1.setUserAccount(ua); is enough to create a strong relationship between UserAccount and contact.most of the time there will not be cases where i save the userAccount with contact but yet the helper method that set the association in both side and which is in UserAccount will not been called before i save the contact for a particular userAccount.So i'm little confused about that and suspecting that setting of the association is part of the why something is not working properly.and then calling session.get(UserAccount.class, ua.getID()) i think goes what i want and i'ld like getUserAccountByEmail to do the same.
3. ChssPly76 thinks the mapping has to be rewrite.So i'm willing to let you guide me through this.I really need to know the proper way to do this because we can't lean everything from a good book.So i you think i should change the mapping just show me how.and probable i'm doing things the wrong way without even been aware of that so don't forget i'm still learning java itself.THanks for the advise and remarks and thanks for reading this
I agree with you that it seems likely that the associations between your parent objects and their child collections are not getting persisted properly. I always like to start out by looking at what is in the database to figure out what's going on. After you run your test what do you see in the actual database?
It seems likely that one of two things is happening (using UserAccount as an example):
The items in the child collection are not getting saved to the database at all, in which case you'll be able to see in the database that there are no records associated with your UserAccount. This could be caused by saving the UserAccount object before you've added the child object to the UserAccount's collection.
The items in the child collection are getting saved to the database, but without the needed association to the parent object, in which case you'll see rows for your child items but the join column (ie 'userAccount' will be null). This could be caused by not setting the userAccount() property on the child object.
These are the two scenarios that I've run into where I've seen the problem you describe. Start by taking a look at what goes into your database and see if that leads you farther.