1

I recently had thoughts on handling the flat vs nested architecture when designing a RESTful API.

Would there be anything wrong with having a system where your nested resources can be specified within a query parameter of the API request? This is only needed in certain complex situations, otherwise you would just access it regularly through collections/instance/collections/instance.

Example: Lets say we need to get a thread with its posts comments users.

Flat architecture way:

getThreadWithPostCommentUsers(threadUuid) {
    const thread = new Thread();

    // by default flat, however becomes lasagna code.
    // data conscious, layer heavy, flexible.
    this.http.request('threads/' + threadUuid).then((threadData) => {
        Object.assign(thread, threadData);

        this.http.request('threads/' + threadUuid + '/posts').then((posts) => {
            thread.posts = posts;

            posts.forEach(post => {
                this.http.request('posts/' + post.uuid + '/comments').then((comments) => {
                    post.comments = comments;

                    comments.forEach(comment => {
                        this.http.request('users/' + comment.user_uuid).then((user) => {
                            comment.user = user
                            console.log(comment.user);
                        });
                    });
                });
            });
        });
    });
}
  • Big amount of layers, lasagna code
  • Flexible for data efficient multi-usage.

This feels the most encapsulated and straight forward. However it starts to become a bit redundant and unfavorable when you deal with alot of relationships.

Nested architecture way:

getThreadWithPostCommentUsers(threadUuid) {
    const thread = new Thread();

    // by default has nested objects, very simple but inefficient
    // not data conscious, not layer heavy, not flexible
    this.http.request('threads/' + threadUuid).then((threadData) => {
        Object.assign(thread, threadData);

        thread.posts.forEach(post => {
            post.comments.forEach(comment => {
                console.log(comment.user);
            });
        });
    });
}
  • Small amount of layers
  • Not flexible for data efficient multi-usage.

The nested way is nice from a convenience perspective but isn't very flexible if you care about minimizing data.

So my idea was to combine the two, where I can reap the benefits of a simple flat architecture, but also have the benefits of loading nests in one request.

Flat and nested hybrid way:

getThreadWithPostCommentUsers(threadUuid) {
    const thread = new Thread();

    // by default flat, adds nests depending on resources parameter
    // flexible, data conscious, not layer heavy
    this.http.request('threads/' + threadUuid + '?resources=posts.comments.user').then((threadData) => {
        Object.assign(thread, threadData);

        thread.posts.forEach(post => {
            post.comments.forEach(comment => {
                console.log(comment.user);
            });
        });
    });
}
  • Small amount of layers
  • Flexible for data efficient multi-usage.

My requests would be flat by default, however my requests have the ability to call on a demand basis for certain nested resources without creating a bunch of custom routes and being data efficient.

Authorization would still be done on the serverside, allowing only certain resources depending on request.

Would this be a bad practice/design choice? What are your thoughts? Is there something out there for this and I am reinventing the wheel?

Thanks

2 Answers 2

1

From the example what you seem to want to do is get all the users who have commented on any post in a particular thread.

Generally if you find yourself doing complex queries to try and get a specific set of data that is a sign that what you actually need is just a new resource

Something like

GET /threads/{threadUuid}/all_users_that_commented

and let the server generate this resource for you.

That resource could be something as simple as a list of users

request

GET /threads/435/all_users_that_commented

response

{
  users: [
    "/users/2324253",
    "/users/363323"
  ]
}

or if you want to sprinkle information into that resource that lets you figure out what post or comment it was that would work too

request

GET /threads/435/all_users_that_commented

response

{
  users[{
    user: "/users/2324253",
    posts_user_commented_on[{
      post: "/threads/435/posts/1231",
      comments[
        "/threads/435/posts/1231/comments/23",
        "/threads/435/posts/1231/comments/42"
      ]
    }, {
      post_id: 421,
      comments[
        "/threads/2324253/posts/421/comments/2",
      ]
    }]
  }, {
    user: "/user/363323",
    posts_user_commented_on[{
      post: "/threads/435/posts/234
      comments[
        "/threads/435/posts/234/comments/23",
      ]
    }, ]
  }]
}

The server will generate this much quicker than the client having to load every post, then every comment, then every user.

And it also means you don't need complex query string to filter data when you already know what you want.

0

It is a common pattern to combine multiple service behind a facade, especially if the link between your client and server would be a bottle neck (especially if it could be a mobile device).

It might be worth checking out GraphQL before you completely re-invent the wheel yourself:

https://graphql.org/

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

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