Question: Any HAL clients or examples of accessing HAL API with admin-on-rest ?
I got started because HAL was mentioned in the first paragraph of the introduction, but now I'm having trouble finding any examples or anyone else using HAL rest client, so I am winding up for now just writing a bunch of simple findAll repositories on top of the already robust existing HAL API.
Adding a more concise answer here that isn't polluted with my thought process now that I've got it all figured out (for anyone's future reference)... Again assuming the HAL API was made with Spring Data Rest.
The four major keys to this integration are:
Exposing foreign key attributes in your JPA entities, which is required in several places by admin-on-rest #Column(name="parentEntity", updatable=false, insertable=false) private Integer parentEntityId;
Exposing all your entity IDs using RepositoryRestConfiguration.exposeIdsFor( MyEntity.class )
Annotate your repositories as #RepositoryRestResource and have them extend PagingAndSortingRepository<MyEntity, Integer>, QueryDslPredicateExecutor<MyEntity> to expose extremely useful search filters by attribute name (e.g. /api/myEntitys?field1=foo&field2=bar).
When submitting create and save requests with foreign keys make sure to adjust your params.data to include the linked resource (e.g. 'http://myserver.com/api/myEntitys/19') on top of (or in place of, HAL has no use for it) the foreign key you exposed in 1. (e.g. myEntityId=19)
Other small items of note:
use PATCH instead of PUT when updating (you may be able to use PUT if you are more of a hibernate expert and can map your entities better than I can but I had trouble getting it mapped perfectly and HAL's PATCH will take partial entities)
When submitting GET_LIST and GET_MANY_REFERENCE you get the total number of items and pagination parameters from the 'page' section of the response, and you use 'size' and 'page' query params in your API requests. (so, no need for headers and stuff)
To change the default 'equals' filter for any string entries (from 3. above) to a 'contains' filter, you will have to also extend QuerydslBinderCustomizer<QMyEntity> and provide your own customize method in each of your repositories. For example:
default void customize( QuerydslBindings bindings, QChampion champion )
{
bindings.bind( String.class ).first( ( StringPath path, String value ) -> path.contains( value ) );
}
We don't have any examples for HAL specifically. However, the point of this introduction was that admin-on-rest is backend agnostic.
You can create your own custom rest client by following the documentation. Read the code of existing ones for inspiration.
For anyone referencing this in the future, if you happen to be in control of your API through Spring Data Rest you can consider the use of an excerptProjection on every one of your existing repositories that shows an inline version of your entity. This would work if there were absolutely nothing besides admin-on-rest accessing your API.
For my case I am planning on writing a custom projection for every rest resource that has entities and naming it the same thing: "inline". Then in the admin-on-rest restClient, just always asking for the inline projection on every GET_MANY or GET_MANY_REFERENCE request.
This is the best I have at the moment. It's not perfect but for the amount of entities I have it's still many weeks faster than building a CRUD interface from scratch so I highly recommend admin-on-rest.
Related
A little background of my problem. I have a set of the following services:
AdapterService - intended for loading certain products from an external system
ApiGateway - accepts requests from UI. In particular, now there is only one request that receives product data to display product in UI from Product Service
ProductService - data storage service for various products. The service itself does not specifically know what kind of product it specifically stores. All types of products are created dynamically by other services that are responsible for these products. Product data is stored as a key-value map (technically it is a json string in DB column)
There is a schema for service interations
So, services in BLUE zone are mine (they can be changed in any way). RED zone describes services of another team (they can't be changed).
Whats the problem
To load product from external system I want to use SpecialProductDto which will store product data. I can use some validation features like Spring annotations and so on. Then to load the product from Adapter Service to ProductService I must transform SpecialProductDto to Map<String, Object> because ProductSerivcie requires it via API.
When I would get product info for UI through ApiGateway, I will need to call ProductService api for getting product that return attribues in Map<String, Object> and then transform this data to some UIReponse which contains some part of product data (because I dont need all product information, just only name and price for example).
But I also want to use SpecialProductDto in my ApiGateway service, because it seems working with Map<String, Object> is error prone... I practically need to fetch data blindly from Map to construct UIResponse. And what if some attribute names will be changed? With Map I only will know it when the request would be made from UI but using special DTO I get such exception in compilation time.
Question
So, what is the best practiсe or maybe patterт should I use in such situation? At the moment I see the following solutions:
Duplicate DTOs in both AdapterService and ApiGateway services. So, any changes in one class must be supported in another
Use Map<String, Object> at my own peril and risk, hoping that nothing will change there
Share SpecialProductDTO between ApiGateway and AdapterSerivce in some separate library and service (seems to be antipattern because of sharing someting can make a lot of problems)
Сan anyone help?
In my opinion, there's nothing wrong on duplicating DTOs.
Also, there's nothing wrong on providing the DTO in a separate library to be imported on each project, you will only be sharing the ProductService's contract and that's it. It does not cause any tight coupling between the Api Gateway and the Adapter. If the contract changes, then it must be changed on all of it's consumers (api gateway and adapter), simple as that.
About using Maps: usually I don't recommend this, because, like you said, you will not be able to take advantages of the built-in Bean Validations that Spring (and other frameworks) provides, but not only that, you'll also, depending on the situation, be using lots of casts and type conversions, which is not good and can be prevented by using DTOs.
Also, be aware that a DTO, in my opinion, should not be named with the suffix of 'DTO'. That's because a name like SpecialProductDTO doesn't clearly states where this object is being used or should be used.
Instead, prefer a something like CreateSpecialProductRequest - this indicates that this object is used when creating a Special Product. Another example is CreateSpecialProductResponse which just represents the response (if needed) after a Special Product creation. Take a look at this StackOverflow answer: Java data transfer object naming convention?
I am building a Spring Boot based application to expose a JSON REST API.
In this application I have a 1-to-many relationship: one Order has multiple Items (and one Item belongs to exactly one Order).
I would like to have the following 4 API endpoints:
GET all Orders: In this case I just want the Order itself - so excluding the associated Items
GET a single Order: get the Order itself including the associated Items
GET single Item: get a single Item including the Order it belongs to (here it does not matter whether just the ID (=primary key) of the order is included or the whole order itself
GET all Items: the all the items; the associated Order is not necessary - but it also would not hurt.
Unfortunately I am a bit lost on how to model my associations and/or controller methods that expose the API endpoints.
Do you have some hints for me?
Thanks a lot!
Your first choice should always be to resort to Software Design Patterns. When developing applications which may require remote connections (or not), there is one that should be implemented in your rest api: Data Transfer Object.
Having into account you are developing under Java/Spring Framework, you should take a look at modelmapper library and to this guide.
I have successfully done the same task in my rest api.
Not sure if there is a better method of doing that, but my approach would be to model and fetch the relations using Hibernate, but in a lazy manner (https://howtoprogramwithjava.com/hibernate-eager-vs-lazy-fetch-type/).
In your controller, you do not return the entity but a DTO class that might be pretty similar to your entity. That DTO is created by some mapper component that provides the logic of including or not including associated items, etc.
I am designing a SpringBoot RESTful API for a Product searching with various attributes (search can be one or more). Few of the criteria are greater than a certain amount and few are less than. In the #RequestParam we can take String or similar values but not any criteria.
My question is what's the best way to get the user data for these criteria in a GET search API call
#GetMapping("/search")
public ResponseEntity<List<OrderView>> searchOrders(...)
{
...
// call to service implementation
...
}
Hmm... https://spring.io/guides/tutorials/bookmarks/ has a good description about REST services with spring. It has also description about the different levels when it comes to RESTful principles (and you can do HATEOAS very simple and clever with spring-boot by HAL).
When you are doing a search you do not have the resource (level1) url but you want to obtain it... So it's okay (imho) to do a simple query parameter call. For example when looking at amazon.com and typing some search parameters there, you will see that they are using a simple approach:
https://www.amazon.de/s/ref=nb_sb_noss?....&url=search-alias%3Daps&field-keywords=criteria1+criteria2+criteria3
They just add the keywords as a concatenated string.
There is also a interesting blog entry from apigee available:
https://apigee.com/about/blog/technology/restful-api-design-tips-search
In this article Spring Boot: How to design efficient REST API?, I explained how to develop a REST API for search. As an example, you found the code and screenshots of postman in this article.
As optimization with one endpoint, I can get several results: resources are sorted, filtered, and paginated.
You do not deal with a lot of code (check of query param and all the control in your controller): the library specification-arg-resolver makes that with no problem
Right now I can't get the concept behind Spring Data REST if it comes to complex aggregate roots. If I understand Domain Driven Design correctly (which is AFAIK the base principle for spring data?), you only expose aggregate roots through repositories.
Let's say I have two classes Post and Comment. Both are entities and Post has a #OneToMany List<Comment> comments.
Since Post is obviously the aggregate root I'd like to access it through a PostRepository. If I create #RepositoryRestResource public interface PostRepository extends CrudRepository<Post, Long> REST access to Post works fine.
Now comments is renderd inline and is not exposed as a sub resource like /posts/{post}/comments. This happens only if I introduce a CommentRepository (which I shouldn't do if I want to stick to DDD).
So how do you use Spring Data REST properly with complex domain objects? Let's say you have to check that all comments does not contain more than X characters alltogether. This would clearly be some invariant handled by the Post aggregate root. Where would you place the logic for Post.addComment()? How do you expose other classes as sub resources so I can access /posts/{post}/comments/{comment} without introducing unnecessary repositories?
For starters, if there is some constraint on Comment, then I would put that constraint in the constructor call. That way, you don't depend on any external validation frameworks or mechanisms to enforce your requirements. If you are driven to setter-based solutions (such as via Jackson), then you can ALSO put those constraints in the setter.
This way, Post doesn't have to worry about enforcing constraints on Comment.
Additionally, if you use Spring Data REST and only define a PostRepository, since the lifecycle of the comments are jointly linked to the aggregate root Post, the flow should be:
Get a Post and its collection of Comment objects.
Append your new Comment to the collection.
PUT the new Post and its updated collection of Comment objects to that resource.
Worried about collisions? That's what conditional operations are for, using standard HTTP headers. If you add a #Version based attribute to your Post domain object, then every time a given Post is updated with a new Comment, the version will increase.
When you GET the resource, Spring Data REST will include an E-Tag header.
That way, your PUT can be conditionalized with an HTTP If-Match: <etag> header. If someone else has updated the entity, you'll get back a 412 Status code, indicating you should refresh and try again.
NOTE: These conditional operations work for PUT, PATCH, and DELETE calls.
I am working on a REST service which uses Spring 4.x. As per a requirement I have to produce several different views out of same object. Sample URIs:
To get full details of a location service: /services/locations/{id}/?q=view:full
To get summary of a location service: /services/locations/{id}/?q=view:summary
I have thought of two solutions for such problem:
1. Create different objects for different views.
2. Create same object, but filter out the fields based on some configuration (shown below)
location_summary_fields = field1, field2
location_detail_fields = field1, field2, field3
Could someone help me to understand what could be an ideal solution? I am not aware of any standard practice followed for this kind of problems.
Thanks,
NN
In my opinion the best option is to use separate POJOs for different views. It's a lot easier to document it (for example when you use some automated tools like Swagger). Also you've to remember that your application will change after some time, and then having one common POJO could make troubles - then you'll need to add one field to one service and don't expose it through another.
See this article on how google gson uses annotations to convert a Java Object representation to a json format : http://www.javacreed.com/gson-annotations-example/
Since you want two different representations for the same object you could roll your own
toJson method as follows :
a) Annotate each field of you model with either #Summary, #Detail or #All
b) Implement a toJson() method that returns a json representation by examining the annotations for the fields and appropriately using them
If you need an XML representation same thing, except you would have a toXML().