Skip to content

Technical Documentation

Peter Herbst edited this page Jul 11, 2021 · 8 revisions

Technical Documentation

The overall system works as follows:

A user uses the mobile app (React Native) to make use of the Bikenest services. The mobile app communicates with the backend technology (Spring Boot APIs) and is able to (un)lock the Bikenest cage and its Bikespots.

The whole architecture can be found here as an interactive C4 model.

Software Architecture

Overview and background

As already described we develop an end-user app that allows customers to rent a secure parking space for their bikes. One of the most important parts here is the communication with the lock and sensor mechanism which is not a software product, but a hardware product developed by our project partner.
All-in-all a frontend is needed with which the user is able to interact with the backend and the Bikenests and is based on React (Expo). The backend is built with Spring Boot following a microservice architecture. On a Raspberry Pi we run a flask web server using python, that will receive requests from the backend and forward calls to the bikenest hardware via a RS232 serial connection.

Architecture & Design Principles

Backend

The Backend is built in a Microservice Architecture style with Spring Boot. Currently there exist five different Services:

  • Bikenest Service (handles all the Information about Bikenests like their Names, GPS Coordinates or the information about booked spots
  • Booking Service (handles everything relevant for booking a bikespot and accessing the bikenest)
  • Usermanagement Service (stores information about the user and provides authentication mechanisms)
  • Payment Service (stores payment information)
  • API Gateway (forwards requests to the other services)

All of the Spring services are structured similiar (except for the API Gateway): They have their own database (that is contained inside a docker container), requests are processed in Controller classes while the BusinessLogic is handled by service classes. Spring makes heavy use of dependency injection with so called beans. A lot of configuration is made via Java Annotations. The Services itself are also containerized with docker for ease of deployment.

Implementation details:

  • Authentication is done via JWT (JSON Web Tokens), these contain a payload (in our case the user id, name, email and role) and is signed using a private key. Usually this means, a service would retrieve the given JWT from the Authorization Header of the HTTP Request and validate that it has not been changed with the private key. In our microservice architecture, the Usermanagement service contains the functionality to validate and create a JWT. In the API Gateway we use a Filter that processes every request, checks the Authorization header and if there is a JWT present, it will send the JWT for validation to the Usermanagement service. If the JWT is indeed not valid, the request is just forwarded without the Authorization Header. All of the services also have another Filter that processes all requests, but these filters use the JWT if it is present in the Authorization Header without validating it again. For every service the controller methods then have the full access to the payload of the JWT. Since the JWT contains the unique id of the user, all data inside the databases is linked to user ids.

  • Restricting access to authenticated users only is handled by the Spring WebSecurityConfigurerAdapter. It is configured, which URLs require Authentication and if it is missing, Spring will automatically return a 401 (Unauthorized) response.

  • Inter Service Communication is done via FeignClients. These are defined in a common gradle project, than is included by every microservice. The FeignClients allow accessing endpoints of the other microservices. Some controller endpoints should only be consumed by other services (for example the endpoints that free and reserve Bikespots). These will expect that the Role of the Accessor is Service. Basically the FeignClients just send the Header Authorization: SERVICE that will be recognized by the Filter of the microservices. The API Gateway of course strips away such Authorization headers.

  • Error Handling and good response messages to the frontend are handled by a GlobalExceptionHandler. This Handler basically catches all Exceptions that happen in any controller. Based on the type of the Exception, the Http Response Code is decided. If it's a BusinessLogicException (custom definition), then the message of that exception is placed in the response body and the Status Code 422 (Unprocessable Entity) is sent back. An exception that comes from an invalid request (missing JSON fields), will have the Response Status Code 400 (Bad Request) with the message "Invalid Request". Any other exceptions that are not specified will have the Response Code 500 (Internal Server Error) with the error message "An unknown error has occurred". In every case, the exception is logged to the spring console, which is useful for debugging purposes.

Frontend

The frontend is combined in the App.js which nests all different screens into one NavigationContainer. That allows to separate all different screens into the screens folder. Therefore, each screen has its own .js file, e.g. FindBikeNestScreen.js.
Some of the elements that build up those screens are reusable. That is why we created reusable components in the corresponding folder, e.g. BikeNest_Button.js. Furthermore, the styles folder includes all general style definitions for colours or e.g. headlines.

The services folder contains convenient classes for communication with the backend via HTTP(S) calls. All calls are implemented as Promises, which means in the successful case, the backend definitely returned a positive response. To handle negative responses, first up in the Promise Chain the Status Code of the Http response is checked. All unsuccessful promises are "converted" into a exception that contains the error message from the Backend. This means, that all callers of the service functions have to catch possible errors from the promises and can then display the error information to the app user.

Testing

For the Backend there are unit and integration tests for each service. Unit tests just test single functions independent of other components, therefore all dependencies have to be mocked. They are more lightweight and easier to execute than integration tests. For integration tests the whole backend has to be started, so that all other microservices and databases are accessible. After that all of the microservice will be started individually and the tests are executed. The files testing-unit.bat and testing-integration.bat will execute all necessary commands for testing. Basically they make use of docker and gradle.

Additionally the tests are also automated using Github Actions.

API Documentation is provided via Postman