How to design rest endpoint URLs for sub resources - java

Let's consider we have two entities. User entity can have many offers and an Offer must have one User.
class User {
...
#OneToMany(mappedBy = "user", orphanRemoval = true)
private Set<Offer> offers;
}
class Offer {
...
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "fk_user")
private User user;
}
At this moment there are two controllers. UserController and the OrderController. The UserController is mapped under /api/v1/users/ and the OrderController is mapped under /api/v1/orders/.
What should an endpoint look like that fetches the user's offer list?
Should it be in the same controller? I do have by functionality project structure.
How to modify or delete an Offer for a particular User? In case we would have /api/v1/users/{username}/offers/{offerId} to delete or update an offer, should we have also /api/v1/offers/{offerId} endpoint that allows to edit or remove an offer? Perhaps it is worth having it for an administrator?

General rules of thumb that I use when creating my endpoints are:
URLs need to be clean and easy to understand
They should be as short as possible while still informative.
Try to build it in a such manner that it allows you to reuse it within the reasonable amount
Think about the user experience(whether this is called from browser or mobile app etc.)
I am not sure if there is a written rule exactly how one should build an URL though.
In your specific case I would use /users/{username}/offers/{offerId} only if this is the only place you are exposing and using offers, since you are separating your code by functionality.
If you have any further logic around offers and/or have beans that have such logic I would create a separate controller for Offers which would be under /offers.
Concerning your last question. This very much depends on what you are trying to achieve. If you need to be able to update/delete/create offers then it makes sense to have such functionality. Even if it only used by the administrator. You can restrict the access to the endpoint. How to do that depends on the way you are authorize your users and the information that you have on them. Most people use roles.
If you decide to have the full CRUD functionality I would suggest to use a single path with combination of request methods.
Personally I would create the following:
#RestController
#RequestMapping(value = "/users")
class UserController {
#GetMapping("{userId}/offers")
public Set<Offer> getAllOffers(#PathVariable("userId") String userId){
...
}
#GetMapping("{userId}/offers")
public Offer getOffer(#PathVariable("userId") String userId, #RequestParam(required = true) String offerId){
...
}
#PutMapping("{userId}/offers")
public Offer createOffer(#PathVariable("userId") String userId, #RequestBody Offer offer){
...
}
#PostMapping("{userId}/offers")
public Offer updateOffer(#PathVariable("userId") String userId, #RequestBody Offer offer){
...
}
#DeleteMapping("{userId}/offers")
public void deleteOffer(#PathVariable("userId") String userId, #RequestParam(required = true) String offerId){
...
}
}
In this scenario I think the POST/PUT for create and update will be cleaner as there will be no duplication of information. To be precise the IDs.

I agree that it should be in the same 'UserController', it makes sense because offers belong to the user, so having an endpoint like:
#GetMapping("{user}/offers")
public Set<OfferDTO> getOffers(#PathVariable("user") String user) {
return offerService.getOffers(user);
}
You can define special DTOs for getting the meta-data from the offers if you wanted to display them in a list for example, and you could display them as a list to your user.
You could set up a similar endpoint for updating the offer which could be a POST endpoint, and a DELETE endpoint for deleting. You might want to think about what would happen if the user is looking at an offer when you delete it though, like making an asynchronous task for deleting the offer in a background thread and updating the UI to inform the user that the offer is deleted.
Spring has some really nice annotations for security stuff (check this and this), you could write your own annotation for administrator endpoints:
#Target(ElementType.METHOD)
#Retention(RetentionPolicy.RUNTIME)
#PreAuthorize("hasAuthority('" + ROLE_ADMIN + "')")
public #interface IsAdmin {}
Then annotate your method like this:
#DeleteMapping("/{user}/{offer}/delete")
#IsReceiverAdmin
public void delete(#PathVariable("user") String user, #PathVariable("offer") String offer){
return offerService.delete(user, offer);
}
Of course the implementation of the service layer would be quite important, but it could be as simple as calling your repositories and performing the operations there :)

Related

Should duplicate values in aggregate roots be checked in the domain layer or the application layer?

I am new to DDD, and I have ran into a problem with unique constraints. I have a problem where one of the fields (a value object) on my aggregate root cannot be a duplicate value. For example, a user has a unique username.
My domain layer contains:
public class User {
private UUID id;
private Username username;
private User(UUID id, Username username) {
this.id = id;
this.username = username;
}
public void rename(Username username) {
if (!username.equals(username)) {
this.username = username;
EventBus.raise(new UserRenamedEvent(username));
}
}
public UUID getId() {
return id;
}
public Username getUsername() {
return username;
}
public static User create(UUID id, Username username) {
User user = new User(id, username);
EventBus.raise(new UserCreatedEvent(user));
return user;
}
}
Username:
public record Username(String name) {
// Validation on username
}
As well as a simple CRUD repository interface, implemented in the infrastructure layer.
My application layer contains:
UserSerivce:
public interface UserService {
UUID createUser(Username username);
// Get, update and delete functions...
}
And UserServiceImpl:
public class UserServiceImpl implements UserService {
public UUID createUser(Username username) {
// Repository returns an Optional<User>
if (userRepository.findByName(username).isPresent()) {
throw new DuplicateUsernameException();
}
User user = User.create(UUID.randomUUID(), username);
repsitory.save(user);
return user.getId();
}
}
This solution doesn't feel right, as preventing duplicate usernames is domain logic, and should not be in the application layer. I have also tried creating a domain service to check for duplicate usernames, but this also feels wrong as the application service has access to the repository and can do this by itself.
If the user was part of an aggregate I would do the validation at the aggregate root level, but as user is the aggregate this isn't possible. I would really like to know the best place to validate the unique constraint.
EDIT: I decided to take VoiceOfUnreasons advice and not worry about it too much. I put the logic to check for duplicates in the application service, as it makes for readable code and works as expected.
This solution doesn't feel right, as preventing duplicate usernames is domain logic, and should not be in the application layer.
There are at least two common answers.
One is to accept that "domain layer" vs "application layer" is a somewhat artificial distinction, and to not get too hung up on where the branching logic happens. We're trying to ship code that meets a business need; we don't get bonus points for style.
Another approach is to separate the act of retrieving some information from the act of deciding what to do with it.
Consider:
public UUID createUser(Username username) {
return createUser(
UUID.randomUUID(),
username,
userRepository.findByName(username).isPresent()
);
}
UUID createUser(UUID userId, Username username, boolean isPresent) {
if (isPresent) {
throw new DuplicateUsernameException();
}
User user = User.create(userId, username);
repository.save(user);
return user.getId();
}
What I'm hoping to make clear here is that we actually have two different kinds of problems to address. The first is that we'd like to separate the I/O side effects from the logic. The second is that our logic has two different outcomes, and those outcomes are mapped to different side effects.
// warning: pseudo code ahead
select User.create(userId, username, isPresent)
case User(u):
repository.save(u)
return u.getId()
case Duplicate:
throw new DuplicateUsernameException()
In effect, User::create isn't returning User, but rather some sort of Result that is an abstraction of all of the different possible outcomes of the create operation. We want the semantics of a process, rather than a factory.
So we probably wouldn't use the spelling User::create, but instead something like CreateUser::run or CreateUser::result.
There are lots of ways you might actually perform the implementation; you could return a discriminated union from the domain code, and then have some flavor of switch statement in the application code, or you could return an interface, with different implementations depending on the result of the domain logic, or....
It largely depends on how important it is that the domain layer is "pure", how much additional complexity you are willing to take on to get it, including how you feel about testing the designs, which idioms your development team is comfortable with, and so on.
It should be noted that one can reasonably argue that the definition of "unique" itself belongs in the domain, rather than in the application.
In that case, the design is mostly the same, except that instead of passing "the answer" to the domain code, we pass the capability of asking the question.
select User.create(
userId,
username,
SomeServiceWrapperAround(
userRepository::findByName
))
Or we define a protocol, where the domain code returns representations of questions, and the application code does the I/O and passes representations of the answers back to the domain model.
(and, in my experience, begin questioning whether all this ceremony is actually making the design "better")
This solution doesn't feel right, as preventing duplicate usernames is
domain logic, and should not be in the application layer.
Correct.
I have also tried creating a domain service to check for duplicate
usernames, but this also feels wrong as the application service has
access to the repository and can do this by itself.
Yes, the application service could do the work by itself, but you have taken a conscious decision to create a dedicated layer for those 'tricky' bits where the domain aggregate cannot do the work on its own and you do not want to leak domain knowledge into the application service.
There's nothing wrong with this. Just don't let 'Domain Service' become a your default approach whenever something looks a bit tricky. You'll end up with an anaemic domain model and all the logic sitting in Domain Services. But, sometimes, a Domain Service is the only pragmatic solution and feel free to use them when all else fails.
The other alternative is to search up "Domain Events". They keep a better separation in my view but demands more effort and plumbing.
Here's a C# introduction, but just as applicable to the world of java.
https://learn.microsoft.com/en-us/dotnet/architecture/microservices/microservice-ddd-cqrs-patterns/domain-events-design-implementation

Spring Boot: Data access depending on role

For my REST-API within a data-warehouse I need some role-based data access. First lets clarify the requirements with some small example. We define to entities Author and Book, both use the PagingAndSortingRepository for their default behaviour. An author can "own" several books, where an book can only depend to one author.
The simplified entities should look like the following:
#Entity
#Table(name = "Author")
public class Author{
// [..]
#OneToMany(mappedBy = "author")
private List<Book> books;
}
#Entity
#Table(name = "Book")
public class Book{
// [..]
#ManyToOne
#JoinColumn(name = "Author_ID")
private Author author;
}
Then I define two roles User and Admin. Normally an author is a common user, so the authorization mechanise only adds a SimpleGrantedAuthority for the role User. But there are some special authors which additionally have the role Admin.
When a normal author with role User calls the url \books, he should only get the books he own, while an an author with role Admin should get all books that exist. Also for the PUT/PATCH/DELETE request authors with the User role, should only be able to update / delete their own books, while the Admin role is able to do this for all books.
My Question: Is there a way to define the data access once in the Controller class? I know something like that from the Django-Framework, where I can override the method get_queryset(), which provides the dataset to work with for every "view"-method (GET/LIST/CREATE/UPDATE/etc.). The way I currently archive this, is to define the methods in the controller for the different API-endpoints and than mange the access there. Which causes two problems:
A lot of work, to implement the methods in the controller
If you have many dependencies between your entities, which is the case for my dwh, you can easily miss some endpoint. As result, I might have an endpoint, where every author has full access, no matter which role.
I thinks this should be a common problem, but I couldn't find a common solution yet. So I am thankful for every advice.
Edit: Example of "secured method"
#RequestMapping(value = "/dimensionAttributeValues", method = RequestMethod.GET)
#ResponseBody
public PagedResources<DimensionAttributeValue> getDimensionAttributeValues(Pageable pageable, PersistentEntityResourceAssembler persistentEntityResourceAssembler) {
Page<DimensionAttributeValue> result;
if (SecurityUtils.userHasRole(ADMIN) || SecurityUtils.userHasRole(TIMEMANAGER)) {
result = dimensionAttributeValueService.getAllDimensionAttributeValue(pageable);
} else {
result = dimensionAttributeValueService.getUserDimensionAttributeValue(SecurityContextHolder.getContext().getAuthentication().getName(), pageable);
}
PagedResources<DimensionAttributeValue> resources;
resources = this.toResource(result, persistentEntityResourceAssembler);
// TODO: Remove dirty Hack!
Link searchLink = linkTo(DimensionAttributeValueController.class).slash("/dimensionAttributeValues/search").withRel("search");
resources.add(searchLink);
return resources;
}
if you want to do it at repository level, spring security gives you access to the principal in the repo.
You need anyway to define a custom query for that.
Something similar to what described here: https://www.baeldung.com/spring-data-security in chapter 3.2
Otherwise you can add Service layer and than use the #PreAuthorize annotation
You could add a where condition to your repository call to filter the result by roles.
Therefor check this method of the JpaSpecificationExecutor:
Page<T> findAll(#Nullable Specification<T> var1, Pageable var2);
Example (Can't test it right now, but this should lead you in the right direction) :
import static org.springframework.data.jpa.domain.Specifications.where;
..
Specification<Book> roleFilter = (root, query, criteriaBuilder) -> criteriaBuilder.equal(root.get("role"), "Admin");
Page<Book> page = this.yourRepository.findAll(where(roleFilter), pageable);

Hiding sensitive information in response

I am currently working in a project where I have a User model and am using a REST API to fetch a list of users. (I have more entities.)
User has a password field. I do not want to include the password field in the result. So I excluded it in the DTO. But when I want to create a User, I want to include the password in the request. So Spring MVC gets the User entity (not the DTO).
I don't think it is good to do so.... For example, I have Event model which is connected to user with a many-to-many relationship. I don't want that in the request. I want only the user. So what do you suggest me to do? Have another kind-of DTO?
Use #JsonIgnore with Access.WRITE_ONLY for getter methods only.
Example
#JsonProperty(access = JsonProperty.Access.WRITE_ONLY)
private String password;
If you are using Jackson to serialize your response objects, you can annotate the property in question with #JsonIgnore and it will not be included in the response.
public User {
private String email;
#JsonIgnore
private String password
...getters and setters
}
It might also be a good idea to create separate response objects that only include the fields you want in case you add sensitive fields down the road and forget to hide them. Likewise, you would also have separate request objects for creating users that would include a password field. Business entities, like a User, are probably best to use only internally, so you can control what information goes public.
To avoid using #JsonIgnore, you can use json-view library.
For example, in your controller you can do something like this:
At first, declare this in your controller variable:
private JsonResult json = JsonResult.instance();
And then use this method:
#RequestMapping("/get/{id}")
public void getUserById(#PathVariable(value = "id") long id) {
User user = usersService.findOne(id);
json.use(JsonView.with(user)
.onClass(User.class, Match.match()
.exclude("password").exclude("yetAnothertopSecretField")));
}
It returns JSON without excluded fields.
The JsonView and JsonResult classes are imported from the json-view library.
I'm tried this JsonProperty.Access.WRITE_ONLY and it's working with me.
#JsonProperty(access = JsonProperty.Access.WRITE_ONLY)
Make the field 'password' as null while sending the response and Jackson will not show that in response. Don't remove it completely from the model class.

Spring And Hibernate - generic entity updates

I have a very simple task,
I have a "User" Entity.
This user has tons of fields, for example :
firstName
age
country
.....
My goal is to expose a simple controller for update:
#RequestMapping(value = "/mywebapp/updateUser")
public void updateUser(data)
I would like clients to call my controller with updates that might include one or more fields to be updated.
What are the best practices to implement such method?
One naive solution will be to send from the client the whole entity, and in the server just override all fields, but that seems very inefficient.
another naive and bad solution might be the following:
#Transactional
#RequestMapping(value = "/mywebapp/updateUser")
public void updateUser(int userId, String[] fieldNames, String[] values) {
User user = this.userDao.findById(userId);
for (int i=0 ; i < fieldsNames.length ; i++) {
String fieldName = fieldsName[i];
switch(fieldName) {
case fieldName.equals("age") {
user.setAge(values[i]);
}
case fieldName.equals("firstName") {
user.setFirstName(values[i]);
}
....
}
}
}
Obviously these solutions aren't serious, there must be a more robust\generic way of doing that (reflection maybe).
Any ideas?
I once did this genetically using Jackson. It has a very convenient ObjectMapper.readerForUpdating(Object) method that can read values from a JsonNode/Tree onto an existing object.
The controller/service
#PATCH
#Transactional
public DomainObject partialUpdate (Long id, JsonNode data) {
DomainObject o = repository.get(id);
return objectMapper.readerForUpdating(o).readValue(data);
}
That was it. We used Jersey to expose the services as REST Web services, hence the #PATCH annotation.
As to whether this is a controller or a service: it handles raw transfer data (the JsonNode), but to work efficiently it needs to be transactional (Changes made by the reader are flushed to the database when the transaction commits. Reading the object in the same transaction allows hibernate to dynamically update only the changed fields).
If your User entity doesn't contains any security fields like login or password, you can simply use it as model attribute. In this case all fields will be updated automatically from the form inputs, those fields that are not supose to be updated, like id should be hidden fields on the form.
If you don't want to expose all your entity propeties to the presentation layer you can use pojo aka command to mapp all needed fields from user entity
BWT It is really bad practice to make your controller methods transactional. You should separate your application layers. You need to have service. This is the layer where #Transactional annotation belongs to. You do all the logic there before crud operations.

Passing complex JPA Entities to the controller with POJO

My team is coding an application that involves editing wikipedia-like pages.
It is similar to the problem we have with registration:
A straightforward implementation gives something like
public static void doRegistration(User user) {
//...
}
The user parameter is a JPA Entity. The User model looks something like this:
#Entity
public class User extends Model {
//some other irrelevant fields
#OneToMany(cascade = CascadeType.ALL)
public Collection<Query> queries;
#OneToMany(cascade = CascadeType.ALL)
public Collection<Activity> activities;
//...
I have read here and there that this fails. Now, in Play!, what is the best course of action we can take? There must be some way to put all that data that has to go to the server in one object, that easily can be saved into the database.
EDIT: The reason this fails is because of the validation fail. It somehow says "incorrect value" when validating collection objects. I was wondering if this can be avoided.
SOLUTION: Changing the Collection to List has resolved the issue. This is a bug that will be fixed in play 1.2 :)
Thanks beforehand
this works. To be more clear, you can define a controller method like the one you wrote:
public static void doRegistration(User user) {
//...
}
That's completely fine. Just map it to a POST route and use a #{form} to submit the object, like:
#{form id:'myId', action:#Application.doRegistration()}
#{field user.id}
[...]
#{/form}
This works. You may have problems if you don't add all the field of the entity in the form (if some fields are not editable, either use hidden inputs or a NoBinding annotation as described here).
EDIT: on the OneToMany subject, the relation will be managed by the "Many" side. That side has to keep the id of the related entity as a hidden field (with a value of object.user.id). This will solve all related issues.
It doesn't fail. If you have a running transaction, the whole thing will be persisted. Just note that transactions are usually running within services, not controllers, so you should pass it from the controller to the service.

Categories