2

I have a simple app for storing the movies, that exposes REST API. I am using spring-boot. I am using this simple app as an example, however, the question is more about general good practice.

I am using spring-jpa, so i have entity that represents the movie:

@Entity
@Table(name="movie")
public class MovieEntity{...}

In my controller, i map the incoming request to MovieRequest object.:

public class MovieRequest{
   private String name;
   private Date created;
   private List<String> genres;
   
   // getters,setters and so on
}

and ofc controller:

@PostMapping(value = "/")
public void addMovie(@RequestBody MovieRequest movie, HttpServletRequest httpServletRequest) {
    ...
}

I also have a service, that handles the operation on the movie.

public class MovieService{
 
   ....
} 

However, the MovieRequest may not contain all pieces of information I need ( as it holds only informations sent via REST API. For example IP address who added the movie, or when the movie was added, geolocation of user who adde the move or literally anything else that is not input from user, and thus I created simple class Movie that could hold additional informations

public class Movie{...}

Now, the MovieService consume Movie object instead of MovieRequest so first thing i need to do is to map it in controller:

@PostMapping(value = "/")
public void addMovie(@RequestBody MovieRequest movie, HttpServletRequest httpServletRequest) {
    Movie movie = mapRequestToMovie(movie);
    movieService.add(movie);
}

So far, i am onlymanipulating with movie and i have created 3 objects: MovieRequest,MovieEntity,and Movie.

Now i want to use HATEOS, and thus i need some class to extend RepresentationModel. The most logical thing for me right now is to create another class, MovieResponse for this:

public class MovieResponse extendsd RepresentationModel<MovieResponse>{}

And i am left with 4 classes for 1 "entity" in my app. If i had 10 i would end up with 40. The structure of my app right now looks like this:

src
 main
  java
   com
    movieApp
      controller
      | request
      |  | MovieRequest
      | response
      |  | MovieResponse
      | MovieController
      service
      | movie
      |  | impl
      |  | | MovieServiceImpl
      |  | Movie
      model
      | MovieEntity
            

At this point, every package expose the class it requires. However as i mentioned, for one "entity" i created 4 objects. And it seems too much redundant, as many attributes are shared ( but not all ). Is there any good practice for this? Is there any guidance?

Yes, i could use Annotation-processor to generate MovieResponse for example but i would still have 4 different objects.

What is the best way to look at "problem" like this? Is there any objective solution, good practice, design pattern?

Thanks for help!

3 Answers 3

3

I have a couple of different things in no particular order:

  1. What does this application do? Is this yet another CRUD "application"? If yes, a better solution is to spin up a DB and access it through SQL. No coding necessary, and you get lots of features in addition.

  2. You need to have it through HTTP. Well, most DBs support that these days out of the box. Still no code necessary.

  3. Your package structure is about technical details. Instead it should be about your app. Your top level is "controller, service, model". That is entirely useless to me as a reader. If it would be "rent, buy, browse" for example, I would already know a ton about your app.

  4. Having "objects" that are just a bag of "properties" is about the worst thing you can do in an object-oriented environment. Having static/singleton methods a close second. This is likely the fundamental reason for your problems. Objects are meant to expose behavior. This is not just a random thing. It is to object-orientation what not having side-effects is for functional programming.

  5. You use technology, like Spring and JPA/Spring-JPA, that are leading / forcing you into bad design decisions. Don't let libraries / frameworks influence you. Think about the problem, do the design right, and only then figure out what library will work with that design.

  6. REST, and especially HATEOAS, is not just about providing links. It is about keeping a lot of choices on the server side. Like changing URIs without notice, changing the order in which resources need to be used (i.e. changing the process itself), introducing new representations for existing resources, and many more. What I'm trying to say here. Why HATEOAS? Is there any particular reason to do it? Again, if not, don't bother. Fun fact: There are no clients that can deal with HATEOAS properly at this time.

Hey, you asked :)

1

"And i am left with 4 classes for 1 "entity" in my app. If i had 10 i would end up with 40."

Don't do that; don't follow a generic set of rules that are centered purely around creating certain structure and don't take into account how that structure supports (or hinders) the needs (use cases, business logic) of the application. This is how you, very systematically, get complex code that's hard to maintain (note, though, that some companies will misguidedly insist on this). The reason is, a structure such as this is based on a set of rules that are arbitrary with respect to the actual needs/problems you're trying to solve, and with respect to the way the application is going (to turn out to) evolve over time. These rules or best practices may have worked in the context of someone else's application, but that's no guarantee that they'll work well for what you're doing.

Shift your perspective

Instead of centering your structure around the entities, think about the most typical uses of you API, and provide endpoints that support that - and design the structure around the entities to support these uses.

Don't think of the API as of something that exposes the entities. The entities are your internal representation that's has a form designed to support your internal code and internal computations. Your API is exposing services or "resources" that consumers need; it's externally-facing. A consumer isn't asking "give me a movie", where "movie" is your internal entity. Rather, the consumer is saying something like "give me a movie overview" - where "movie overview" is a collection of data they'd expect to see on something like an IMDB page on that movie (for example). They don't care if you internally represent this as one entity, or three, or if you change these representations over time - that's a problem for you to deal with behind the scenes. Or they might ask for "movie details" or "cast and crew", etc. Note that for some of these requests, you might pull from the same underlying data. It's not a 1-to-1 mapping (but it shouldn't be arbitrarily different either - see the section on Entities below).

So it's about the semantics of the requests, not about the entities. This doesn't work if you have 4 different levels of abstraction that are all pretty much the same as the entity, just with a different type. You might start out this way, but you must not insist on keeping these structurally in sync as your application evolves just for the sake of "consistency"; doing so defeats the purpose of having them in the first place.

What you need to do is (1) analyze (existing or probable) usage patterns and figure out how to break down these so that you can support consumer needs with a relatively small number of requests going back and forth, and (2) find some suitable internal representation (suitable set of entities) that balances these external needs with the needs of your internal code.

What about the entities?

As for the entities themselves, the way you conceptualized them now (the classes you chose, the data each class contains, etc.) may work fine in this new light, but this new direction may suggest ways to change your domain model as well. For example, while the domain model can differ from data structures you use for your inputs and outputs, if the domain model is drastically different in a way that makes translating it back and forth very tedious and/or hurts the usability of your application - then likely the discrepancy is more costly than it is beneficial, and you need to rethink it.

You learn more and more about the domain (== the narrow problem area your application specifically deals with) as you develop, which means that your original conception of what entities you have in the system (what they look like, etc.) was created when you understood the domain the least - and this is perfectly normal. As you learn more, you might get ideas on how to re conceptualize your domain model to better suit your application - don't get hung up on the first thing you came up with. Eventually, it should stabilize to something that works well for you, but you want to organize your code so that it allows you to do that.

Keep it simple and don't be afraid to go around the framework

That said, don't overcomplicate things - try to keep it as simple as you can for as long as possible (in the spirit of YAGNI). What you have is: a set of external requests organized around external needs, a set of data structures at the boundary that support each need, and your internal code (including the entities). As long as this set of data structures and functions at the boundary doesn't change too often, you should be able to restructure the code inside your controllers and within your domain model fairly independently as you develop, until you arrive at something that's nice to work with in terms of making changes and doing maintenance. Also note that frameworks will sometimes get in your way here, so if you need to deviate from the practices recommended for the framework, and doing so isn't too painful, don't be afraid to do it.

P.S. A note on HATEOAS

Now i want to use HATEOS, and thus i need some class to extend RepresentationModel.

HATEOAS comes from the original REST thesis, which is about generalized architectural principles of applications running on huge, Internet-scale heterogeneous networks, and has very little to do with what the industry calls REST (or RESTful services). It's an architecture for a "multi-organization, anarchically scalable information system" - like the Internet. The original REST is not about the sorts of web service APIs that we normally deal with; it's all one giant mixup we're stuck with now.

The thing about the industry REST, is that it's not using HATEOAS at all, and I suspect neither are you - and you don't need to, so don't worry about it. This is kind of funny, because REST stands for "representational state transfer", and the thing that's core to state transfer is HATEOAS.

HATEOAS is not about having some distinct representation model type (RepresentationModel<MovieResponse>). It's about not maintaining client-specific application state on the server, while simultaneously enabling the client to be oblivious to how a specific application "flows" (+ it interplays in a certain desirable way with other design goals of the Web). HATEOAS stands for "hypermedia as the engine of application state". To understand, think about something like navigating through a web page, or perhaps following a set of steps from page to page to fill out some registration form or whatever. That's sort of like an application going through different states, having a certain application-specific flow of steps: now you're at this step, and these are the things you can choose, etc., and now you're at a different step.

Client-server interactions on websites are (or should be) stateless; the server doesn't track the steps, or even the data you entered (until the final submit); the requests and responses themselves carry all the information needed for each party to figure out what's going on. That is, the information about the current state for a specific client is transferred back and forth. This helps the server scale, and lets some of the computations to be offloaded to clients. This works because there's an agreed upon way (a standard) that allows for this state (the current screen, the current values of fields, etc.), as well as state transitions (links, forms) to be encoded in the message itself - HTML on top of HTTP. So the browser doesn't need to understand the steps for a particular website/application, the server just tells it what the transitions are and where to place them. The browser simply knows how to render them and how to let the user interact with them, but it has no idea what they mean beyond that. That's HATEOAS.

Note that in RPC-style RESTful APIs this never really happens - the client application always has embedded knowledge about multiple endpoints, about what they mean and often has special handling for each. And if a screen has links, the client either creates these links itself based on that knowledge (and these links may or may not reach back out to the API), or it asks the API for a very specific set of links, that most often access resources whose meaning client code very much understands. That is, the application-specific aspects of the interaction are driven by assumptions encoded in the programming of clients and the server themselves, while in HATEOAS, application-specific aspects of the interaction are entirely contained in the data exchanged. That's not necessarily easy to do, and it's probably not needed for what a typical web service under the control of a single party/organization is doing.

0

OK so the answer is you expose your actual object in your REST service, you don't make a special Request or Response version of it. That way lies the madness you describe.

But how what about all the extra bits the user cant populate on add? Just overwrite them in the controller. Or better yet let the client set them, Or store them in a separate audit record or something and keep them completely off your model.

What about HATEOAS? Honestly? just don't do it, it adds nothing but bandwidth and pain. But if you have to, use generics.

EntityWithLinks<T>
{
   T Entity
   Link[] Links
}

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.