Skip to content

restful api implementation

Janne Tahkola edited this page Apr 26, 2024 · 35 revisions

Important information for Deadline 3

‼️  This chapter should be completed by Deadline 3 (see course information at Lovelace)


📑  Chapter summary In this section you must implement a RESTful API. The minimum requirements are summarized in the Minimum Requirements section of the Project Work Assignment. If you do not meet the minimum requirements this section WILL NOT be evaluated.

CHAPTER GOALS

  • Implement a RESTful API
  • Write tests for the API

✔️     Chapter evaluation (max 21 points) You can get a maximum of 21 points after completing this section. More detailed evaluation is provided in the evaluation sheet in Lovelace.

RESTful API implementation

List of implemented resources

📑  Content that must be included in the section A list of all resourcess. Each resource should include its URL, a short description and supported methods. You should mark also which is the name of the class implementing the resource (if you have implemented such resource) Consider that you do not need to implement every resource you initially planned.   The minimum requirements are summarized in the Minimum requirements section from the Project work assignment.

✏️ List your resources here. You can use the table below for listing resources. You can also list unimplemented resources if you think they are worth mentioning

Resource name Resource url Resource description Supported Methods Implemented
Users /users Read or create users GET, POST x
User /users/{id} Read or modify a user GET, PUT x
User roles /users/{id}/roles Read or create user role associations GET, POST x
User role /users/{user_id}/roles/{role_id} Read or delete a singular user role association GET, DELETE x
Current user /current-user Same as GET /users/{id} but takes the user id from the bearer token GET x
Roles /roles GET x
Role /roles/{id} GET x
Role privileges /roles/{id}/privileges Read or create role privilege associations GET, POST x
Role privilege /roles/{role_id}/privileges/{privilege_id} Read or delete a singular role privilege association GET, DELETE x
Privileges /privileges GET x
Login /auth/login POST x
Logout /auth/logout POST x

Basic implementation

💻     TODO: SOFTWARE TO DELIVER IN THIS SECTION The code repository must contain:
  1. The source code for the RESTful API 
  2. The external libraries that you have used
  3. We recommend to include a set of scripts to setup and run your server
  4. A database file or the necessary files and scripts to automatically populate your database.
  5. A README.md file containing:
    • Dependencies (external libraries)
    • How to setup the framework.
    • How to populate and setup the database.
    • How to setup (e.g. modifying any configuration files) and run your RESTful API.
    • The URL to access your API (usually nameofapplication/api/version/)=> the path to your application.
Do not forget to include in the README.md file which is the path to access to your application remotely.

NOTE: Your code MUST be clearly documented. For each public method/function you must provide: a short description of the method, input parameters, output parameters, exceptions (when the application can fail and how to handle such fail).  In addition should be clear which is the code you have implemented yourself and which is the code that you have borrowed from other sources. Always provide a link to the original source. This includes links to the course material.


✏️ You do not need to write anything in this section, just complete the implementation.


RESTful API testing

💻     TODO: SOFTWARE TO DELIVER IN THIS SECTION The code repository must contain:
  1. The code to test your RESTful API (Functional test)
    • The code of the test MUST be commented indicating what you are going to test in each test case.
    • The test must include values that force error messages
  2. The external libraries that you have used
  3. We recommend to include a set of scripts to execute your tests.
  4. A database file or the necessary files and scripts to automatically populate your database.
  5. A README.md file containing:
    • Dependencies (external libraries)
    • Instructions on how to run the different tests for your application.
Do not forget to include in the README.md the instructions on how to run your tests. Discuss briefly which were the main errors that you detected thanks to the functional testing.

Remember that you MUST implement a functional testing suite. A detailed description of the input / output in the a REST client plugin.

In this section it is your responsibility that your API handles requests correctly. All of the supported methods for each resource should work. You also need to show that invalid requests are properly handled, and that the response codes are correct in each situation.


Main errors solved with testing were unexpected changes in response format (letter case, null fields inclusion) or content type (plain json, Hal, Hal-forms). Also correct error responses.


REST conformance

📑  Content that must be included in the section Explain briefly how your API meets REST principles. Focus specially in these three principles: Addressability, Uniform interface, Statelessness. Provide examples (e.g. how does each HTTP method work in your API). Note that Connectedness will be addressed in more depth in Deadline 4.

Addressability: All resources - user, role, privilege, and their collections - are identified by URIs with the HTTP schema. Non-collection URIs contain a unique identifier that refers to a single item of that resource.

Uniform interface: HTTP methods combined with resource URIs are used as unique resource locators for reading/modifying resources. HATEOAS and the HAL media type are used to provide the resource URIs, and HAL-FORMS media type can be used to request the available methods and potential templates for each. HATEOAS allows us to (mostly) decouple the client from the server's URI structure, so that URIs and even templates may be modified without affecting the client in a huge way.

Statelessness: A bearer access token is used to access the APIs - no server-side session is kept. Authorisation is handled during the request based on the access token, which only states that "the user making this request has an identifier x, and has successfully authenticated recently enough that we trust it".


Extras

📑  Details on extra features This section lists the additional features that will be graded as part of the API but are not required. In addition to implementing the feature you are also asked to write a short description for each.

URL Converters

📑  Fill this section if you used URL converters Write a short rationale of how URL converters are used, including your thoughts on the possible trade-offs. Go through all URL parameters in your API and describe whether they use a converter, what property is used for converting, or why it's not using a converter.

Spring provides converters for Java base types such as Integer, and URI to entity conversion is not used for simplicity's sake, so nothing extra is implemented on that front.

All responses however are converted into HATEOAS based resource representations that may contain links. Support for HAL+JSON and HAL-FORMS media types are enabled via the Spring HATEOAS library, which configures Spring to produce responses that adhere to these specifications, depending on the requested media type.

In practice, the method definitions for each endpoint declare some Java class as their return type. These classes extend the RepresentationModel class which comes with a links field and methods for managing them. Most of the time, each class representing a database entity has a corresponding representation model. This separation is done so that the differences between database and REST operations are easier to manage.

Entity models are converted into representation models using "assemblers" that map the fields and augment the created response models with relevant links. Links are created by either

  • Using reflection-based utilities from Spring HATEOAS which produce links from given REST resource classes and their methods (i.e. the request handlers)
  • Using the methods provided by RepresentationModel to create IANA-based self links, e.g. when adding a Location header after a POST request

Problems

Both HAL and HAL-FORMS are quite restrictive in their spec and by extension so is Spring's support for them. HAL does not allow controls but instead requires using e.g. HAL-FORMS on top of it if you want to expose modifying operations and their templates via the response.

For example, the HAL-FORMS templates that Spring produces do not allow the target URLs to be templated URLs, meaning that it forces the API design to be pretty "granular". You can't have a template with a link targeting a singular resource, unless the template comes as part of that singular resource, where you know and are able to provide it the exact resource IDs. Things like {id} are removed from template target URLs.

I caved in to the spec and now return these DELETE links when the full URL is known. Due to this, the link list returned by GET /user/{user_id}/roles will include two links for each role:

  • a link with the rel self that refers to the role as a user-role association (users/{user_id}/roles/{role_id})
  • a link with the rel role that refers to it as its own resource (/roles/{role_id}).

An additional GET /user/{user_id}/roles/{role_id} was implemented to represent the association separately. This GET is not really needed or used. From practicality point of view this is just unnecessary bloat on the API, but it did force it to become more semantically correct.


Schema Validation

📑  Fill this section if you used JSON schema validation Write a short description of your JSON schemas, including key decision making for choosing how to validate each field.

All schemas are declared as Java classes using Jackson and Java Bean API annotations in conjunction with the Hibernate Validator. Jackson is used to define processing rules such as letter case or read-only fields, or whether to fail when encountering unknown fields. Java Bean API annotations tell the Hibernate validator how to validate objects and fields for e.g. missing or incorrect values.

Both are integrated into Spring Boot so that we can mostly survive by simply adding the desired annotations to the Java classes that represent a request or response schema. We can, for example, declare that an API should consume JSON only, and Spring will use a Jackson mapper that's preconfigured for JSON to deserialise the request body, and then validate the resulting Java object further with Hibernate Validator. Same goes for response serialisation, meaning the resulting JSON must be valid.

The schemas are fairly simple, consisting of mainly required, non-null or non-blank validations for string fields. The User schema contains a couple fields with more complex validations:

  • username: required, not blank, must match a regex pattern and size constraints
    • Each user must be identifiable by a username that does not cause e.g. SQL injection risks
  • password: required, not blank, must match size constraints
    • Each user must have a password that is not too short for security reasons. The only character restriction is that whitespaces are not allowed. SQL injection is not a concern because it's stored as a base 64 encoded hash
  • created_at: not null, read only

The same schema is used for GET, POST and PUT methods, thus password is required only with POST requests and created_at is managed by the database.


Caching

📑  Fill this section if you implemented server side caching Explain your caching decisions here. Include an explanation for every GET method in your API, explaining what is cached (or why it is not cached), and how long is it cached (and why). If you are using manual cache clearing, also explain when it happens.

Hibernate provides a L1 cache out of the box, which is enabled by default via Spring's OSIV pattern. In practice this means that each queried entity within the same request is cached and reused. This applies to the Users API as a whole, and while this isn't a common use case, it's there nonetheless.

Because the Users API is called quite a lot by the auxiliary service, a more comprehensive cache such as Hibernate's L2 cache would be ideal. This has not been implemented yet. The problem is that for the first time ever, I chose to let Hibernate manage everything instead of writing the database schema manually. This means that associations are handled with Hibernate abstractions, which leads to some complications that I need to solve for the L2 cache to work properly.


Authentication

📑  Fill this section if you implemented authentication Explain your authentication scheme here. Describe the authentication requirements for each resource in your API, and your reasoning for the decisions. In addition, provide a plan for how API keys will be distributed, even if the distribution is not currently implemented.

A successful username/password authentication generates a JWT that should be included as a bearer token in all calls to restricted resources. Logout will revoke the token by storing the JWT ID. This storage is checked on each request for the ID of the given bearer token.


Resources allocation

Task Student Estimated time
Everything Janne Tahkola ~30h