SwisssignChallenge
Requirements
- GraalVM 23
- PostgreSQL 17, but a Docker Compose file is available for your convenience. If you decide to run it locally, please check application.yml for default values.
Generate sources
To generate code from the API contract, run ./mvnw generate-sources
Build native executable
- Make sure your
JAVA_HOME
orGRAALVM_HOME
environment variable is correctly pointing to your JDK. - Run
./mvnw -Pnative native:compile
.
Note that you don't need to build a native executable to run a development environment.
Choices
GraalVM
It's my first time using GraalVM, but I took the opportunity of using it in this project because of its small scale. Its goals are to improve the performance of JVM-based applications to match native languages.
Startup times are also greatly improved; less than 0.5 seconds. The drawback is that the build times are greatly lengthened; around 1 minute for an empty project.
PostgreSQL
I don't think I have to explain much about PostgreSQL, but it's well-suited for both small and large-scale projects. Also, support with Spring Boot is, in my experience, flawless.
Flyway
I'm experienced with Liquibase, but after quickly reading about errors related to GraalVM, I understood it was unsupported. Flyway is another popular migration tool, so I gave it a go. I have nothing to say for or against it.
JWT
I knew the basics but never implemented the authentication layer myself. JWT is a standard, but in an enterprise environment there would probably be an OIDC-compliant service to be plugged into.
After initializing the correct beans, it worked really well.
As I don't specify the JWT secret, all issued tokens are not recognized after a restart and must be regenerated (i.e. all users are logged out).
LLMs were a great help here too, along with good ol' Baeldung.
Files
At first, I wanted to store the files as a BYTEA in the database. I thought
this would be much simpler and minimalistic, but I was wrong on the first point.
I gave up after I couldn't find the root of the error "cannot convert to long",
despite not having a single long in my source code. Anyway, files are stored
on the filesystem and are retrieved by the PdfServiceImpl
. This service makes
sure that the file name doesn't contain ".."
in the file name in order to
prevent directory traversal attacks.
Files are not uploaded by the user so there is virtually 0 chance of it containing
malicious characters, but I thought it was important to safeguard against this.
UUID
As there is insanely little chance of collision, UUID are most useful in
distributed systems where we could give the responsibility of generating
the UUID to the clients. For a system like this, I doubt that the max value
of 64-bit integers would be reached, but the maintainability cost isn't much
higher than using integers. If anything, it at least prevents the user from
using the ID in the wrong place (i.e. DELETE /person/1
instead of
DELETE /city/1
) as no entity shares the same ID.
json-patch
I used json-patch to confirm
the status of documents. Instead of sending the whole representation, only
the operation (op
), the path (path
) to apply it to and the new value
(value
) have to be supplied.
Potential improvements
A lot more tests. Integration tests, fuzz tests and benchmarks are those that come to mind.
I would also spend a lot more time understanding cyclic dependencies when (de)serializing
JSON. For now there's a manual workaround in SigningRequestDocumentServiceImpl.java
.