Bookmark AngularJS frontend (direct access) - Spri

2019-08-14 05:59发布

问题:

I've started to implement a web-front end to manipulate a RESTful service served by Spring Data Rest over a traditional relational DB. This is done with AngularJS and the angular-hal library that I found fits very well the HATEOAS philosophy and formalism of Spring Data Rest.

For example, I just need to know the first API endpoint ('/'), and then all of my queries are done through the relations without caring for the urls. The problem I have is to be able to directly access the page displaying one of my entities.

Let's take the example of a contacts repository. If I start from the home page, then navigate through the contact list, then choose the contact I want to see in details, there is no problem.

But I can't access directly the page showing the details of the contact: the resource is injected from the list controller to the edit controller, and the edit controller is not able to know the url to request if not told by the list controller.

The root problem is that with Spring Data Rest, entities have no public field for their id (not in JSON), and the repositories have no API relation to search by id.

I thought about some solutions, but I don't like any of them.

1. Work around in the backend

  • Add a Projection with method getId() to all the entities which needs to be bookmarked
  • Add the corresponding findById() in the repository interface
  • Use the id in the url of the frontend as a parameter (or path) and resolve the resource by calling the new available search relation.
  • Drawbacks : this force us to redo manually all the DTO generated automatically by Spring Data Rest, so we are loosing one of the purpose of the framework.
  • Question : is there a way to configure Spring to automatically expose those id fields and findById methods ?

2. Use self relation

  • Get the self URI of the entity with angular-hal $href('self') method
  • Use it as a parameter of the page and resolve the resource by calling a new halClient.$get(resourceUri) on it
  • Drawbacks: the uri should be processed before and after beeing put in the page url to prevent errors due to another "http://" in the adress bar. I thought of doing some base64 encoding on it, but this is consuming.
  • Drawbacks: this exposes the API url to the world, and this data could be critical and need to stay hidden (even if this will be accessible through a debugger watching network traffic too).

3. Forget about HATEOAS

  • Don't bother with the relations and the discovery capability
  • Removes angular-hal and just use $resource with plain old url mappings
  • Drawbacks: this is going backward, and feels like not following guidelines...

So, am I missing something ? What is the best practice regarding the access of data in a full RESTFul HATEOAS environment ?

回答1:

I've found the exposeIdsFor methods that makes possible to add the field annotated with @Id in JSON.

@Configuration
public class RepositoryConfiguration  extends SpringBootRepositoryRestMvcConfiguration {

    /**
     * add here the main resources that would need direct access from outside
     */
    @Override
    protected void configureRepositoryRestConfiguration(RepositoryRestConfiguration config) {
        config.exposeIdsFor(Contact.class, User.class);
    }
}

Then I made all the entities I want the id to be exposed to inherit from a common abstract class

@MappedSuperclass
public abstract class AbstractEntity {

    @Id @GeneratedValue
    Long id;

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }
}

this abstract class is served by a @NoRepositoryBean repository

@NoRepositoryBean
public interface AbstractEntityRepository<T extends AbstractEntity> extends JpaRepository<T, Long> {    
    @RestResource(rel = "byId")
    T findById(@Param("id") Long id);
}

and then I can expose both id and method byId() for the entities I care :

@Entity
public class Contact extends AbstractEntity {

    @Column 
   String name;

    @Column 
    String email;
}

public interface ContactRepository extends AbstractEntityRepository<Contact> {    
}

I think this makes a good work around and allow the direct access of the entities from the client at a small price