User Interface: Web API - i-on-project/integration GitHub Wiki

Web API

The user interface layer is responsible for handling all user interaction, even if the user is another machine, via a simple web API. Due to its place on top of the architectural diagram, no other layer has knowledge or can interact with the UI layer, thus making it easily replaceable in the future, should the need arise, without affecting the rest of the system.

All UI related components are located in the org.ionproject.integration.ui package.

Job management is the sole purpose of the UI Layer, and it is achieved by exposing a small set of resources that allows users to create new Job executions, query currently running or pending jobs and obtain details about a specific job execution.

Resource HTTP Method Purpose
/jobs GET Retrieve all running jobs
/jobs/{id} GET Retrieve details about a specific job
/jobs POST Create a new job execution
/swagger GET Swagger UI based on Open API Specification

The full API specification is available in OpenAPI format through the /swagger resource. Every request that triggers a job execution will launch it asynchronously and immediately return a job ID that can be used to track its status and execution parameters.

Due to the Integration project already being built using the Spring framework it made sense to use another Spring module to implement the Web API. Specifically, the Spring Boot Starter Web module. The package consists of a main module, the Job Controller that implements the API endpoints described earlier, and two supporting modules:

  • Input: contains the InputProcessor and DTOs to hold incoming request data.
  • Output: holds outbound DTOs and logic to convert Job information received from the application core into their output representations. Each valid web request will result in a call to the Application Layer’s Job Engine.

Authentication

API requests must submit a Base64 encoded token in the Bearer Token format. The application expects only one valid token which is configured and passed through the TOKEN environment variable.

Authentication is handled using Spring Security through the configuration of request filters that can use custom logic to "block" each request and return an appropriate response (e.g. a [401 response](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/401 when no authentication is provided).

All security related configurations are located inside the org.ionproject.integration.ui.configpackage.

Request Filters

Request filters are configured through the WebSecurityConfig class. WebSecurityConfig defines which filters, and in which order, should be applied to incoming requests. In our scenario every request is allowed and forwarded to our custom filters AuthFilter and FilterChainExceptionHandler.

AuthFilter

AuthFilter extends OncePerRequestFilter, an abstract Spring Security class that, as the name suggests, is executed once per request.

AuthFilter overrides two base methods: doFilterInternal and shouldNotFilter. shouldNotFilter is used to indicate requests that should NOT be filtered which, in our case, are only requests meant for the /swagger endpoint.

doFilterInternal defines the actual filter behavior. It receives a FilterChain object that must be called if the received request is allowed to proceeded (i.e., it is not filtered).

Annotated version of the current doFilterInternal implementation to better illustrate its behavior:

    override fun doFilterInternal(
        request: HttpServletRequest, // The incoming web request.
        response: HttpServletResponse, // The response object.
        filterChain: FilterChain // The filter chain. Can be called to forward the request to the next filter.
    ) {
        val token = getToken(request) // Utility method to obtain the request's token

        if (token == null || token != validToken) // If no token is provided or it does not match the expected token then fail
            throw InvalidTokenException() // This throw will stop execution of this method, thus preventing the request to proceed to the next filter

        filterChain.doFilter(request, response) // Token OK, forward request to the next filter
    }

FilterChainExceptionHandler

This Filter, configured to before `AuthFilter, simply forwards requests to the next filter under normal operation but its purpose is to catch exceptions thrown along the filter chain and forward them to the exception resolver to ensure the appropriate response status code and body contents are set (using the Problem+Json format).

Input Processor

The InputProcessor, used to handle incoming job creation requests, exposes a single method through its interface: getJobRequest(CreateJobDto): AbstractJobRequest, which receives a DTO containing the body of the incoming web request, validates its contents and returns a JobRequest object which can then be safely submitted to the Job Engine. This validation checks whether all mandatory parameters have been filled (not null or blank) and if the requested Job Type is valid. The Job Type, as defined in the Application Layer, can be for one of the three presently supported information document types: timetables, evaluation schedules and academic calendars.