Starting a new project is always a mix of excitement and tough decisions, especially when you're stitching together familiar tools like Google Docs with powerhouses like GitHub Pages. This is the story of buildingÂ
Introducing gdocweb
Before we dive into the technical intricacies and the decision-making labyrinth of buildingÂ
I decided to buildÂ
Here's a short video explainingÂ
https://www.youtube.com/watch?v=aaDBFVx6qC8&embedable=true
Java 21 and Spring Boot 3.x: Innovation and Maturity
When you're spearheading a project on your own, like I was withÂ
Java, being a mature technology, offered a sense of reliability even in its latest iteration. Similarly, Spring Boot 3.x, despite being a newer version, comes from a lineage of robust and well-tested frameworks. It's a conservative choice in the sense of its long-standing reputation but innovative in its features and capabilities.
However, this decision wasn't without its hiccups. During the process of integrating Google API access, I had to go through a security CASA tier 2 review. Here's where the choice of Java 21 threw a curveball. The review tool was tailored for JDK 11, and although it worked with JDK 21, it still added a bit of stress to the process. It was a reminder that when you're working with cutting-edge versions of technologies, there can be unexpected bumps along the road, even if they are as mature as Java.
The transition to Spring Boot 3.x had its own set of challenges, particularly with the changes in security configurations. These modifications rendered most online samples and guides obsolete, breaking a lot of what I had initially set up. It was a learning curve, adjusting to these changes and figuring out a new way of doing things. However, most other aspects were relatively simple, and the best compliment I can give to Spring Boot 3.x is that it's very similar to Spring Boot 2.x.
GraalVM Native Image for Efficiency
My interest in GraalVM native image forÂ
Implementing GraalVM
Getting GraalVM to work was nontrivial but not too hard. After some trial and error, I managed to set up a Continuous Integration (CI) process that built the GraalVM project and uploaded it to Docker. This was particularly necessary because I'm using an M2 Mac, while my server runs on Intel architecture. This setup meant I had to deal with an 18-minute wait time for each update – a significant delay for any development cycle.
Facing the Production Challenges
Things started getting rocky when I started to test the project production and staging environments. It became a 'whack-a-mole' scenario with missing library code from the native image. Each issue fixed seemed to only lead to another, and the 18-minute cycle for each update added to the frustration.
The final straw was realizing the incompatibility issues with Google API libraries. Solving these issues would require extensive testing on a GraalVM build, which was already burdened by slow build times. For a small project like mine, this became a bottleneck too cumbersome to justify the benefits.
The Decision to Move On
While GraalVM seemed ideal on paper for saving resources, the reality was different. It consumed my limited GitHub Actions minutes and required extensive testing, which was impractical for a project of this scale. Ultimately, I decided to abandon the GraalVM route.
If you do choose to use GraalVM, then this was the GitHub Actions script I used. I hope it can help you with your journey:
name: Java CI with Maven
on:
push:
branches: [ "master" ]
pull_request:
branches: [ "master" ]
jobs:
build:
runs-on: ubuntu-latest
services:
postgres:
image: postgres:latest
env:
POSTGRES_PASSWORD: yourpassword
ports:
- 5432:5432
options: >-
--health-cmd pg_isready
--health-interval 10s
--health-timeout 5s
--health-retries 5
steps:
- uses: actions/checkout@v3
- uses: graalvm/setup-graalvm@v1
with:
java-version: '21'
version: '22.3.2'
distribution: 'graalvm'
cache: 'maven'
components: 'native-image'
native-image-job-reports: 'true'
github-token: ${{ secrets.GITHUB_TOKEN }}
- name: Wait for PostgreSQL
run: sleep 10
- name: Build with Maven
run: mvn -Pnative native:compile
- name: Build Docker Image
run: docker build -t autosite:latest .
- name: Log in to Docker Hub
uses: docker/login-action@v1
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Push Docker Image
run: |
docker tag autosite:latest mydockeruser/autosite:latest
docker push mydockeruser/autosite:latest
This configuration was a crucial part of my attempt to leverage GraalVM's benefits, but as the project evolved, so did my understanding of the trade-offs between idealism in technology choice and practicality in deployment and maintenance.
Deployment: VPS and Docker Compose
When it came to deployingÂ
Avoiding Raw VPS Deployment
I immediately ruled out the straightforward approach of installing the application directly on a VPS. This method fell short in terms of migration ease, testing, and flexibility. Containers offer a more streamlined and efficient approach. They provide a level of abstraction and consistency across different environments, which is invaluable.
Steering Clear of Managed Containers & Orchestration
Managed containers and orchestration (e.g., k8s) were another option, and while they offer scalability and ease of management, they introduce complexity in other areas. For instance, when using a managed Kubernetes service it would often mean relying on cloud storage for databases, which can get expensive quickly. My philosophy was to focus on cost before horizontal scale, especially in the early stages of a project.
If we don't optimize and stabilize when we're small, the problems will only get worse as we grow. Scaling should ideally start with vertical scaling before moving to horizontal, vertical scaling means more CPU/RAM while horizontal adds additional machines. Vertical scaling is not only more cost-effective but also crucial from a technical standpoint. It makes it easier to identify performance bottlenecks using simple profiling tools.
In contrast, horizontal scaling can often mask these issues by adding more instances, which could lead to higher costs and hidden performance problems.
The Choice of Docker Compose
Docker Compose emerged as the clear winner for several reasons. It allowed me to seamlessly integrate the database and the application container. Their communication is contained within a closed network, adding an extra layer of security with no externally open ports. Moreover, the cost is fixed and predictable, with no surprises based on usage.
This setup offered me the flexibility and ease of containerization without the overhead and complexity of more extensive container orchestration systems. It was the perfect middle ground, providing the necessary features without overcomplicating the deployment process.
By using Docker Compose, I maintained control over the environment and kept the deployment process straightforward and manageable. This decision aligned perfectly with the overall ethos ofÂ
Front-End: Thymeleaf Over Modern Alternatives
The front-end development ofÂ
React: Modern but Not a One-Size-Fits-All Solution
React is undeniably modern and powerful, but it comes with its own set of complexities. My experience with React is akin to many developers dabbling outside their comfort zone - functional but not exactly proficient. I've seen the kind of perplexed expressions from seasoned React developers when they look at my code, much like the ones I have when I'm reading complex Java code written by others.
React’s learning curve, coupled with its slower performance in certain scenarios and the risk of not achieving an aesthetically pleasing result without deep expertise, made me reconsider its suitability forÂ
The Appeal of Thymeleaf
Thymeleaf, on the other hand, offered a more straightforward approach, aligning well with the project's ethos of simplicity and efficiency. Its HTML-based interfaces, while perhaps seen as antiquated next to frameworks like React, come with substantial advantages:
- Simplicity in Page Flow: Thymeleaf provides an easy-to-understand and easy-to-debug flow, making it a practical choice for a project like this.
- Performance and Speed: It’s known for its fast performance, which is a significant factor in providing a good user experience.
- No Need for NPM: Thymeleaf eliminates the need for additional package management, reducing complexity and potential vulnerabilities.
- Lower Risk of Client-Side Vulnerabilities: The server-side nature of Thymeleaf inherently reduces the risk of client-side issues.
Considering HTMX for Dynamic Functionality
The idea of incorporating HTMX for some dynamic behavior in the front-end did cross my mind. HTMX has been on my radar for a while, promising to add dynamic functionalities easily. However, I had to ask myself if it was truly necessary for a tool likeÂ
In summary, the choice of Thymeleaf was a blend of practicality, familiarity, and efficiency. It allowed me to build a fast, simple, and effective front-end without the overhead and complexity of more modern frameworks, which, while powerful, weren't necessary for the scope of this project.
Final Word
The key takeaway in this post is the importance of practicality in technology choices. When we're building our own projects, it's much easier to experiment with newer technologies, but this is a slippery slope. We need to keep our feet grounded in familiar territories while experimenting.
My experience with GraalVM highlights the importance of aligning technology choices with project needs and being flexible in adapting to challenges. It’s a reminder that in technology, sometimes the simpler, tried-and-tested paths can be the most effective.
Also published here.