2

I know HATEOAS can be a bit of a heated topic. Some people like it, some people don't. That is not what I want to discuss.

What I want to discuss is what technology other than HATEOAS (or some form of telling the client what can be done with an entity) can be used to prevent the client from writing the same logic that the API has. And this is perhaps subjective, but it seems to me that I see a lot of (web)apps that have the same entity logic on the client and server and I don't understand why.

In this case I would like to propose that the client is a SPA to indicate that the server can't render the page differently based on the state of an entity; the SPA has to do this.

For example:

Let's say we have a Blog entity which has a list of Comment entities. Let's say that adding a comment is no longer allowed when the Archived property of the Blog is set to true. Let's also say that adding a comment is not allowed when the amount of Comments is 100 or more. We also say that adding a comment is not allowed when the user is not logged in.

At my workplace we use HATEOAS for this. We don't really use the URLs that the API returns (you might even say we don't use it 100% correctly?); it's mostly about the links that are returned which are really useful for us. For example, let's say this is a Blog returned from api/blogs/3

{
  "id": 3,
  "title": "Why StackExchange is great!",
  "text": "Lorem Ipsum",
  "archived": false,
  "comments": [
    {
      "id": 1,
      "user": "john@example.org",
      "text": "Great post, man!",
      "links": [
        {
          "rel": "delete",
          "href": "api/blogs/3/comments/1",
          "method": "DELETE"
        }
      ]
    }
  ],
  links: [
    {
       "rel": "addComment",
       "url": "api/blogs/3/comments",
       "method": "POST"
    }
  ]
}

This makes it very easy for our client to decide if it should render a "add comment" button! If we can find a HATEOAS link where the rel is addComment, we do so. The API can even take care of the authorization for us. If the user that performed the request is not logged in, the link also won't be returned. Same goes for deleting a comment, I think you get the point! :)


The great benefit in my eyes is that we don't need to write the logic twice in the front-end and back-end. I think it speaks for itself, but this has several benefits as well for example less code, fewer (complicated) unit tests, and a smaller bundle size.

If in the future a blog is allowed to get new comments until there are 200 comments instead of 100, ideally we would only need to change code in our Blog entity (or, less broadly speaking, the server) and no changes to the/any client(s) would need to be made, tested, or deployed!

What are your views on this? Are there other/better standard technologies/ways to prevent duplicate logic?

Thanks!

3
  • I don't think there are many ways that are fundamentally different. If you are going to keep the business logic and application state logic on the server, then you have to send to the client, alongside the requested resource, a description of some sort that lets the client determine what state transitions & actions are there leading out of the current state. So the format of the response may be different, but the idea is fundamentally the same. Now there's also this idea of Computational REST (CREST), where instead of documents, you exchange executable code. Nov 13, 2020 at 15:47
  • Shouldn't the addComment link be missing from your example since the archived property is true?
    – Bogdan
    Nov 13, 2020 at 18:36
  • @Bogdan yeah you're right, i'll fix my example :) Nov 13, 2020 at 23:01

3 Answers 3

2

Are there other/better standard technologies/ways to prevent duplicate logic?

Not really. Removing duplicate logic from the client depends a lot on:

  • the way you build the API;
  • the way you document the API;
  • discipline of the client developers.

The first point is obviously about the same approach as HATEOAS, to have the server API drive the client: these are the results, these are the possible transitions, these are the available options, etc, just be "a dumb client" and follow what the server says.

The second point is about not exposing any of the logic rules to the server. You say "adding a comment is no longer allowed when the Archived property of the Blog is set to true. [...] adding a comment is not allowed when the amount of Comments is 100 or more. [...] adding a comment is not allowed when the user is not logged in". The client should not know these things. Do you have a link with rel=addComment? Render it and point it to the link address it comes with. You don't have one? Do nothing then. Basically, you document how to interact with the API, not how the API itself functions. A browser works with any web page because it knows how to interact with the HTML and all the media types exchanged, it knows nothing about how each visited site works and what logic they have.

The third point is the most important. This approach, as well as HATEOAS work only if the client also implements the behavior, not just the server. As a developer I can chose to ignore your rel=addComment links and figure out for myself that you can't add comments if the post is archived based on observing a few responses and a few blog posts. I can also see that you POST a comment to URLs like api/blogs/{blog-id}/comments so instead of following links returned by the server, I can just use that as a template and build my own URLs and use them instead. And everything works, until the server changes the links and now it doesn't work anymore.

I think the main problem is really related to discipline, with developers getting used to creating and consuming HTTP APIs (not true RESTful services) that describe an URL structure of the application and document what to send and how, and when, and the original idea of "hypermedia driven" ended up as "documentation driven".

0

You could put the logic as Code-on-Demand, which is an optional constraint for REST.

This means that your API will return an executable code (e.g. JS snippet, or something written in a domain specific language), that is interpreted by the client to make those decision on the client side.

With CoD, the logic is still mostly opaque to the client; all that has to be preprogrammed in the client is how to interpret the code, but the logic that the code runs can be changed dynamically by the server.

Generally HATEOAS is simpler to implement then CoD, which is why I usually recommend using HATEOAS as much as possible in the API before deciding to use CoD.

As an example for this, your API may return:

{
  ...
  "comments": [
    {
      "id": 1,
      "user": "john@example.org",
      "text": "Great post, man!",
      "links": [
        {
          "rel": "delete",
          "href": "api/blogs/3/comments/1",
          "method": "DELETE",
          "shouldDisplay": "user.isLoggedIn && !blog.archived && comment.age() < 60*5"
        }
      ]
    }
  ],
  ...
}

If you want comments to be deletable only within 5 minutes of creating it.

3
  • What would be the benefit of this? In our workplace, we (would) only add the "delete" hateoas when user is logged in, the blog isnt archived and the comment is less than 5 minutes old. That way the client doesn't need use that CoD. And even if the client were to use the CoD there could be a higher chance of failure because of wrong interpretation or bugs, IMO. Nov 14, 2020 at 15:07
  • @S. ten Brinke: If your UI requirement is such that you'll need to re-evaluate the CoD code many times or if the code depends on some client side state, the benefit of expressing this as CoD is that you can rerun the code without a round-trip to the server. For example if you have an SPA, you can rerender the screen every thirty seconds or so, and that code will hide the delete button after 5 minutes without needing to refresh. I agree with you that when you don't need that kind of flexibility, HATEOAS makes a lot more sense because of the reasons you started.
    – Lie Ryan
    Nov 14, 2020 at 15:19
  • In the majority of cases, CoD is overengineering. But it is an option that can be useful under the right situations. You could also express more than just button visibility, for example you can have CoD for "postDelete" action, which can be used to run arbitrary code after the button is deleted. The possibilities for CoD are very wide and very diverse, but it's also one that may restrict the API to clients that have a suitable interpreter. In HTML for example, this CoD is the <script> tag or the onclick/onsubmit/etc attributes, which people now use it for various things.
    – Lie Ryan
    Nov 14, 2020 at 15:25
0

It seems to me that you have restricted the problem to such a degree that it can have no answer.

If the server cant render the View, because the client must do it, the the client must include the view logic.

You could leave that logic off the server, but you would be trusting that the client hasn't been bypassed.

I would argue that the solution to your problem is server side rendering.

1
  • I don't really see this as a problem, I see this question more as growing my understanding and learning about other options :). But server side rendering is a good shout, a great way to look at this :) Nov 17, 2020 at 12:49

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.