Enable service methods in REST API to be called in a resource oriented way

Let me explain better…

Currently we need to define several service methods if we want to deal with custom CRUD scenario (not enabled by default via entities endpoint, for a variety of business reasons).

This goes against REST principles… resources should be accessed using standard HTTP methods, but now we are stuck with calls like:

services/someservice/createSomething (POST)
services/someservice/getSomething (GET)
services/someservice/updateSomething (POST)
services/someservice/deleteSomething (POST)

while we should have instead:

services/someservice/something (POST)

services/someservice/something (GET with standard id query param and others) OR services/someservice/something/id that is equivalent to id= query string

services/someservice/something (PUT with id in query string or payload), OR services/someservice/something/id (ignoring id in payload)

services/someservice/something (DELETE with id in query string) OR services/someservice/something/id

dealing with this is not such a big deal if we refactor the ServicesController class to support dynamic method invocation, using a convention based lookup from rest-services.xml file, like so:

for POST on “something”, search for a “createSomething” method with single parameter (body)
for GET on “something”, search for a “getSomething” method and pass query strings to parameters
for PUT on “something”, search for a “updateSomething” and pass the id alongside payload, or if not found fall back to “createSomething” (for example, this should be considered carefully)
for DELETE on “something”, search for a “deleteSomething” and pass the id

Please note that this is not a duplicated “entities” functionality, it is a smart way to let developers build their own “canned down” or “enhanced entities” specifically for their specialized REST clients, that most of the time have different requirements/constraints than standard (vaadin) client.

Thanks
Paolo

Example rest-services.xml with added resource support:


<?xml version="1.0" encoding="UTF-8"?>
<services xmlns="http://schemas.haulmont.com/cuba/rest-services-v2.xsd">
    <service name="app_AnimalService">
        <resource name="animal" postMethod="createAnimal" getMethod="getAnimal"
                  putMethod="updateAnimal" deleteMethod="deleteAnimal"/>
        <method name="shave">
            <param name="animal"/>
        </method>
    </service>
</services>

Advanced stuff like nested resources support could come in the future, but this small addition could make a huge difference in usability IMHO.

Hi Paolo,

I think your idea makes sense. So I’ve created an issue: https://youtrack.cuba-platform.com/issue/PL-9896, we’ll consider it when plan further development of the REST API.

Hi Paolo,

i’m not sure if this approach leads in the right direction. In fact it is not mainly about your improvement which seem to be as a somewhat natural next step when using this REST-service calls. Instead it is more about the idea in general to call services directly through the HTTP interface. Here are my concerns about this idea:

First of all, this feature to call services directly through the REST API always felt odd to me. This is mainly because as the name of REST states, it is all about resources. Calling a service is the exact opposite of this approach: a RPC based communication.

Therefore I always thought that the people that use these REST-service calls don’t at all care about the REST principles (which is ok as well…). But for these people, it does also not matter if the right HTTP verbs are used, because the guarantees that should come with theses verbs (like idempotence and safety) are not relevant for those people as well and most likely they will not implement their business logic so that it will be align with the guarantees of the HTTP verbs.

So basically for this group of people, does not matter (which is fine as well, just wanted to mention it).

However, for the other group of people that really care about HTTP and want to use the protocol exactly as what it is: an application layer protocol, this REST-service call RPC style will not be an option anyway.

There are several reasons for that:
First, the above mentioned mismatch between “resource-orientation” and the “RPC-style communication”.

But there are practical problems as well. In the service there is no possibility to interact with the request / response cycle. But if you want to speak HTTP right, you want to control stuff like the correct response codes, caching information (ETags), custom content types etc. So basically all the stuff that HTTP allows to express.

Instead of trying to make this strange REST-service calls a first class citizen, we could instead try to push the natural solution for this problem: create Spring MVC controllers that will define the HTTP API and deal with all the HTTP based communication and delegate to the service for the actual business logic.

I’ve done that quite often in CUBA and it works just out of the box, but it is not something that is very prominent in the docs (only here: https://doc.cuba-platform.com/manual-6.6/rest_api_v2_custom_controllers.html).

What do you think about it?

Bye
Mario

Hi @mario, these are fair points, and in fact I myself write real resource oriented endpoints with plain Spring controllers.

What I want to introduce in CUBA, is a way to streamline and “guide” less experienced devs to write their own resource-oriented endpoints, and give some guidance on when an RPC style call is not feasible.

That proposal (service called with a resource oriented style), was only a draft, but I’m thinking of proposing another alternative, that is a new main endpoint under the v2 path, called for example resources, with a new rest-resources.xml file, and a series of “helpers” to write REST controllers, with some sane defaults for newbies.

For example a resource could be called WITH or WITHOUT authentication, only by specifying a boolean attribute, or there will be some automatism (not imposed, but opt-in) to serialize and deserialize entities like services do, but this time with more granular control over the process.
I mean, code like this is not something is documented today, and I’d like to give it automatically by convention/configuration:

@PutMapping
public ResponseEntity<String> updateUserProfile(@RequestBody(required = false) String userJson) {
    ServicesControllerManager.ServiceCallResult result =  userProfileControllerManager.updateUserProfile(userJson);
    return buildUserProfileResponse(result);
}

protected ResponseEntity<String> buildUserProfileResponse(ServicesControllerManager.ServiceCallResult result) {
    HttpStatus status;
    String contentType;
    if (result.isValidJson()) {
        status = HttpStatus.OK;
        contentType = MediaType.APPLICATION_JSON_UTF8_VALUE;
    } else {
        status = HttpStatus.NO_CONTENT;
        contentType = TEXT_PLAIN_UTF8_VALUE;
    }
    return ResponseEntity.status(status).header("Content-Type", contentType).body(result.getStringValue());
}

and here the called method in the manager class:

@NotNull
public ServicesControllerManager.ServiceCallResult updateUserProfile(@NotNull String userJson) {
    throwIfNull(userJson);

    MetaClass metaClass = metadata.getClass("sec$User");
    User updateUser;
    try {
        updateUser = entitySerializationAPI.entityFromJson(userJson, metaClass);
    } catch (Exception e) {
        throw new RestAPIException("Cannot deserialize an entity from JSON", "", HttpStatus.BAD_REQUEST, e);
    }

    User updatedUser = userProfileService.updateProfile(updateUser);
    return serializeEntity(updatedUser);
}

protected <T extends Entity> ServicesControllerManager.ServiceCallResult serializeEntity(T entity) {
    if (entity != null) {
        String entityJson = entitySerializationAPI.toJson(entity,
                null,
                EntitySerializationOption.SERIALIZE_INSTANCE_NAME);
        return new ServicesControllerManager.ServiceCallResult(entityJson, true);
    }
    return new ServicesControllerManager.ServiceCallResult("", false);
}

protected <T> void throwIfNull(T requestEntity) {
    if (requestEntity == null) {
        throw new CustomValidationException("empty request body");
    }
}

The (excerpt) before is what I have to do to cope with correct serialization/deserialization with entities (when I “want” to use CUBA entities at all, most of the time I use POJOs).

The point is: give devs sane defaults and clear guidance, so to simplify things, and be able to define REST endpoints even in Studio

Regarding RPC style calls, I do not completely agree with your point. I advocate the Practical REST approach, because purists are never right :wink:
There are times when a simple RPC style call (with clear side-effects) is the simplest solution, call it an “action” or whatever you like.
But I agree that giving RPC calls (aka services) in the hand of inexperienced devs, could lead to a horrible REST api… but that’s “their” problem, that sooner or later will bite them back, and they’ll learn the lesson :slight_smile:

P.

Yes, the simplest solution might be true. This is why I don’t think that not given the user the option to have a direct call to a service via the generic API would be not that good either. Regarding side-effects: surely resource oriented calls can have side effects. Idempontence & saefty just applies to GET & PUT. So one just has to use the correct HTTP verbs for methods with side-effects.

The second approach you suggested seems more valuable from my point of view, because it optimizes the correct thing. Instead of trying to make a cat look like a dog, let’s just take our dog and give it some nice haircut and slide :wink: