Inspired in https://github.com/jonashackt/spring-boot-vuejs
Sample project that shows how to build single-page applications with Undertow and Vue.js. The project is divided in two modules: backend and frontend.
In the backend module we have the following dependencies:
- WEB SERVER: Undertow. (via popok)
- RDBMS: MySQL (provided in a docker image).
- DB CONNECTION POOL: HikariCP.
- IMMUTABLE MODELS: Lombok.
- JSON: Jackson. (via popok)
- HTTP AUTHORIZATION: JWT.
- LOGGING: Log4j2.
- TESTING: JUnit 5.
Spring JDBC (via popok) provides the functionality to use aliases in the SQL queries. One of the solutions is to put prefixes to the column names of the database tables. More info: https://stackoverflow.com/questions/15184709/cachedrowsetimpl-getstring-based-on-column-label-throws-invalid-column-name
Guava is optional, is part of a proof of concept with RequestHandler.java and ResponseHandler.java. This is a work in progress approach.
The web server is configured via app.properties.
The router of the server is an Undertow HttpHandler you can see it in Router.java.
All of the HTTP API handlers are wrapped with BlockingHandler
in Handlers.java
because all of them do blocking operations and we execute all these operations in the pool of workers of undertow. One of the reasons of this is the blocking nature of the JDBC.
Static files are served from the public
folder inside resources
folder. The frontend project compiles all the static files and the maven-resources-plugin
moves the files from /frontend/target/dist
to /resources/public
in the backend module. This happens in the backend module compilation.
The database is configured via .properties
files (thanks to HikariCP that supports them) via db_prod_pool.properties
for production configuration and db_test_pool.properties for testing.
The class responsible of load this config files is: HikariConnectionPool.java.
In order to access to the database, I use raw JDBC with a minimal abstraction to build queries (Query.java) and execute them (Database.java).
To map a table record to an object I made a mapper interface (JdbcMapper.java) to do it manually (the mapping).
One of the problems that I have encountered is the aliases problem: If you want to return the ResultSet
to other layers of your application,
you have to store it in some cache and return to the layers. This is because if you close the ResultSet
you lose the connection and the
ResultSet
would be empty. To avoid this situation you can use the CachedRowSet
class, and then you can close the ResultSet
and the
connection returns to the pool, with the connection closed (returned to the pool) and the record in memory you can map it to an object.
The frontend module is pretty simple is a back-office application to manage the houses and the furniture of it's users.
- HTTPs/HTTP2.
- Redirection from HTTP to HTTPs.
- Gzip compression for static files.
- Authorization via JWT.
- Proxy all frontend requests to our backend.
- Single artifact deploy.
- Minimal JDBC abstraction.
- Migrations.
- Database testing with docker.
- Immutable JSON messages.
- Application configuration.
- OpenJDK 12 support.
- Easy backend and frontend testing.
- Streams data from the backend to the frontend via SSE with Reactor and vanilla undertow.
Demo here, remember to accept risks (self-signed cert), for login use:
admin
admin
- Download JDK13 from: https://jdk.java.net/13/ and put in your machine at:
/opt/prod_jdk
Maven will use this folder in order to compile the project, this allow us to update easily the JDK version.
If you want to have the java
as a command without typing /opt/prod_jdk/bin/java
you can do this:
sudo ln -s /opt/prod_jdk/bin/java /usr/local/bin/java
or (ubuntu based) add this line at the end of your ~/.bashrc:
export PATH=/opt/apache-maven-3.6.2/bin:$PATH
(for maven)
export JAVA_HOME=/opt/prod_jdk
export PATH=$PATH:$JAVA_HOME/bin
- Compile Vue.js project and generate static files.
mvn -U clean install -pl :frontend
- Remove previous static files.
rm -rf backend/src/main/resources/public
- Test migrations (require a running docker).
mvn -U clean -pl :backend -Dflyway.configFiles=src/main/resources/database/test_migrations.properties flyway:migrate
- Compile backend, move static files to public/ and generate jar file (deployable artifact).
export APP_ENV=test && mvn -U clean compile test package -pl :backend
- Run application (change APP_ENV with
prod
ortest
).
export APP_ENV=test && java -Xms64M -Xmx512M -Dlogfilename=web -Dlog4j2.contextSelector=org.apache.logging.log4j.core.async.AsyncLoggerContextSelector -jar backend/target/backend-0.0.1.jar
NOTE: There is a script to deploy locally (local_deploy.sh), use it for development only!.
You don't need a backend framework or an ORM to be able to make web applications, why not plain java?. "Don't reinvent the wheel" they say, and you pick a framework, now you have a bicycle with: a washing machine, an embedded microwave and a neon light beacon... Do you really need all of this features?
Wait a moment... why are you saying this about frameworks?
The problem isn't frameworks, it's us (developers). Try to learn the basics first, later learn a framework. Learn things like OOP, FP, HTTP, client-server model, plain SQL, VMs... and then, when you know these things, when you are tired of doing these things without no help, when you are tired of doing repetitive things only then, take a framework.
Many of the problems that you are going to have with a framework, is due to the lack of knowledge of the framework itself or the basics. Soon you will have a good amount of spaghetti code unmaintainable, with some modules of your project that fits the "framework boundaries" and others that use hacks to fit these boundaries with your knowledge of the language, resulting in performance issues, cryptic errors, updating problems, development delays (slow cycles) and more...