Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add a new post about ms project structure #2096

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions _data/authors.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -541,8 +541,8 @@ dodalovicgran:
bio: "Software Engineer at GRAN Software Solutions GmbH"
iyanev:
name: "Ivelin Yanev"
email: "[email protected]"
emailhash: "05c3ef31aeba2d12e2eb26282752e654"
email: "[email protected]"
emailhash: "1b52ffd3caf3e4673f715df2a883defd"
job_title: "Senior Software Engineer"
twitter: "iqnev"
markobekhta:
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
---
layout: post
title: 'Effective Project Structuring for Microservices with Quarkus'
date: 2024-08-22
tags: howto microservices project-structure
synopsis: 'The article presents a comprehensive guide on how to effectively structure Java microservices projects using Quarkus.'
author: iyanev
---

:imagesdir: /assets/images/posts/howto/iyanev

== Introduction

In the ever-evolving landscape of software development, the adoption of microservices architecture is rapidly gaining traction due to its scalability, flexibility,
and maintainability. Quarkus, as a Java-native Kubernetes stack tailored for _GraalVM_ and _HotSpot_, optimizes Java specifically for containers and enables it
to become an effective platform for serverless, cloud, and Kubernetes environments.

Key to leveraging Quarkus effectively is understanding how to structure your project correctly. Proper project structuring not only enhances
the manageability of the code but also plays a crucial role in the success of deploying microservices architecture. This article delves into the rationale
behind meticulous project planning in Java microservices using Quarkus, the benefits of a well-organized structure, and provides a practical example of a scalable
and efficient project layout.

In this article, we delve into the indispensable role of structured project management in deploying successful Java microservices through Quarkus,
highlighting how an optimized structure upholds the essential characteristics of microservices.

== The Essential Characteristics of Cloud-Native Services

Microservices architecture distinguishes itself by several defining characteristics that Quarkus enhances:

1. **Small and Focused**: Each service in a microservices architecture is designed to perform a single, well-defined task, promoting simplicity and focus. Quarkus supports this by facilitating lightweight, bootable jars and native compilations, which are ideal for specific tasks.

2. **Independent**: Independence allows services to be developed, deployed, and even scaled without reliance on other services. Quarkus's container-first philosophy ensures that each microservice can run in its isolated environment.

3. **Loosely Coupled**: Services communicate via well-defined APIs, reducing the dependency on internal implementations. Quarkus encourages decoupling through standards like _MicroProfile_ and _JAX-RS_, making services interaction seamless and efficient.

4. **Scalable**: Efficiently managing increasing workloads is crucial. Quarkus's reactive programming capabilities enable services to be responsive and scalable under varying loads.

5. **Flexible**: The ability to adapt to changing conditions is facilitated by the dynamic scaling and resilience features of Quarkus, supporting on-the-fly adjustments in a cloud-native environment.

6. **Resilient**: Quarkus enhances the robustness of microservices with built-in fault tolerance and health-check capabilities, ensuring the services are resistant to failures and recover swiftly from interruptions.

== Why a Well-Structured Project Matters?

Project structure in microservices is paramount. It aids developers in navigating the codebase, accelerates the development process, and reduces the
likelihood of bugs. In microservices, where services are often developed and managed by different teams, a clear and consistent structure is essential
for both individual productivity and team collaboration.

Well-defined project structures facilitate onboarding new developers more quickly, enabling them to contribute without extensive ramp-up time. Furthermore, a structured
project helps in automating deployment pipelines and service scalability, which are core attributes of cloud-native applications.

== The Top-Level Structure: Maven Multi-Module

Adopting Maven multi-module project structures has proven effective in managing large-scale Java applications, and Quarkus fully supports this modality. Let's detail the
Maven multi-module structure:

A Maven multi-module project consists of an aggregator `POM` that orchestrates several submodules. This structure is beneficial for dividing a
large application into manageable, independently deployable units that can be maintained and updated with ease. Each module typically has a focused responsibility and can
be deployed independently from others.

Here’s a simplified layout based on a real-world application:

[source, shell]
----
parent (aggregator pom.xml)
├── api (API interfaces and DTOs)
│ ├── pom.xml
│ └── src
├── core (Business logic)
│ ├── pom.xml
│ └── src
├── data-access (Data layer integration)
│ ├── pom.xml
│ └── src
----

This modular approach ensures isolation of service responsibilities, simplifying updates and maintenance. It also enhances code reusability across different parts of the application.

== Detailed Project Layouts Based on Quarkus

Drawing on the principles of robust microservice architecture, I've tailored an optimized project structure specifically for Quarkus that enhances scalability, maintainability, and coherence across diverse development teams. The design, which I originally developed in 2018, has seen several refinements to better suit evolving technological trends. Initially, the structure wasn't specifically crafted with Quarkus in mind; however, throughout its application, it has proven exceptionally compatible with Quarkus's philosophy and coding practices.
Let us examine this structure as implemented in an IDE like IntelliJ IDEA—though it could be effectively adapted to any other modern IDE.

image::ms-structure.png[Microservices Project Structure,title="Microservices Project Structure"]

You can find the full project structure in the GitHub repository: link:https://github.com/iqnev/quarkus-ms-template[quarkus-ms-template]

Starting from the top-level, the project is divided into several modules, each with a specific role:

1. **api-dtos**: This module is crucial for de-coupling the internal representations of data from what is exposed externally through _APIs_. By separating _Data Transfer Objects (DTOs)_ from domain models, the module ensures that any changes—be they in database schema or business
logic—do not directly affect the clients who consume these _APIs_. This separation enhances _API_ stability and versioning.

2. **commons**: This utility module acts as a repository for shared logic, constants, and helpers that are used across multiple other modules in the project. Centralizing common functionalities in this way reduces duplication and fosters consistency throughout the application.
It’s particularly helpful for storing utilities like data format converters, standard validators, or common business rules.

3. **configuration**: Configuration management is streamlined in this module, which consolidates all configuration settings for the entire application in one place, simplifying access and management. This central repository of configurations ensures that settings are consistently applied and managed, simplifying environment
shifts (such as from development to production) and minimizing the potential for errors due to misconfiguration. Beyond simple configuration files, this module is also the ideal location for defining custom managed beans that are essential across the application, such as `ObjectMapper` for JSON serializing and deserializing,
custom deserializers, and other specialized beans

4. **db-entities**: Dedicated exclusively to database interactions, this module encapsulates all _ORM_ classes or database schemas, ensuring they are separated from the business logic and API layers. Segregating entities in this manner helps maintain clean architecture principles
by decoupling data access mechanisms from business rules processing, hence promoting a cleaner and more traceable codebase.

5. **services**: Here lies the core business logic, encapsulated in service classes that operate on domain models and use `db-entities` for data persistence. Each service focuses on a specific business task, enhancing modularity and reusability. This separation also aids in isolating business operations from
direct client interactions, which are handled by controller classes in other modules, thus adhering to the single responsibility principle.

6. *resources*: Amid some debate, this module was named to reflect its alignment with link:https://jakarta.ee/specifications/restful-ws/3.0/jakarta-restful-ws-spec-3.0#resource-classes[JAX-RS], where classes annotated with _JAX-RS_ are referred to as resources. This module handles the creation of _REST_ endpoints, acting as the communication layer that interfaces with external clients. The name 'resources' was chosen over alternatives like 'rest' or 'controllers' to emphasize standardization and adherence to established RESTful principles as described in the Red Hat resource for building REST APIs with Quarkus.
By aligning with standard terminology, the project aims to be intuitive for developers familiar with _JAX-RS_ and REST conventions.

7. **application**: Serves as an umbrella module that brings all the pieces together, ensuring they are correctly wired and ready to deploy.

Each of these modules plays a strategic role tailored to foster ease of maintenance, scalability, and robustness in a cloud-native microservice environment using Quarkus. By adhering to these structured separations, the architecture promotes clarity and efficiency in development cycles, making it easier for
teams to develop, modify, and scale services independently with minimal interference.

The above structure is based on the following Architecture layers which is mentioned in the link:https://developers.redhat.com/articles/2022/02/03/build-rest-api-ground-quarkus-20#architecture_layers__resource__service__and_repository[RedHat resources].

image::resource_layers.png[Source: https://developers.redhat.com/articles/2022/02/03/build-rest-api-ground-quarkus-20#architecture_layers__resource__service__and_repository,title="Architecture layers: Resource, service and repository"]


== Much Abstraction is Too Much

When discussing project structures, a common question arises about the need for the layered approach I previously described, especially when compared to
popular methodologies like _Domain-Driven Design (DDD)_. Here, I evoke the words of David J. Wheeler:


[quote,David J. Wheeler from The C++ Programming Language 4th edition.]
____
All problems in computer science can be solved by another level of indirection, except for the problem of too many layers of indirection.
____



This axiom highlights a critical insight: while abstraction is a powerful tool, excessive abstraction can complicate rather than simplify solutions. In the realm of
cloud-native microservices, which Quarkus is particularly adept at handling, the tendency toward over-engineering can lead toward unnecessary complexity.
The core philosophy behind Quarkus and microservices, in general, is to remain **small** and **focused**. Each microservice is tailored to a specific task, optimized for
performance, and designed to operate independently. This approach naturally guards against the pitfalls of excessive abstraction by emphasizing simplicity and purpose in each service layer.

While _DDD_ is a respected and strategic design approach that incorporates an in-depth analysis of domains to inform modeling, it can introduce unnecessary complexity
when not aligned with the fundamental requirements of cloud-native applications. _DDD_ often encourages deep hierarchies and extensive model interdependencies, which
can obscure the clarity and increase the cognitive load required to understand and maintain the system.

For cloud-native environments where services must be rapidly developed, deployed, and scaled, DDD's extensive abstraction layers can impede these processes. Each additional
layer of abstraction not only distances the developers from the actual functionality and performance characteristics of their application but also amplifies the potential
points of failure.

This leads to a critical issue: when problems arise in a highly abstracted system (as they inevitably do), diagnosing and resolving these problems becomes significantly
more challenging. The troubleshooting process requires navigating through multiple layers of abstraction, each adding a level of complexity to the debugging process.

Our approach with Quarkus is designed to strike a balance between the necessary abstraction for maintainability and the practicality required for
efficiency and speed. By defining a project structure that prioritizes modularity without falling prey to the over-complexities of deep abstraction, we enable
developers to build systems that are robust, scalable, and manageable. Instead of becoming bogged down in layers of indirection, developers can focus on delivering value through clear, concise, and effective microservices.

== Conclusion
Quarkus stands as a highly logical choice for implementing cloud-native services. It appreciates and harnesses the power of microservices architecture without succumbing
to unnecessary complexities. By fostering a project structure that is small, focused, and loosely coupled, Quarkus enables developers to build scalable, resilient, and
independent microservices. This tailored approach, combined with the innate capabilities of Quarkus,
promotes efficient application development and deployment in modern cloud environments, making it the go-to framework for Java microservices in the cloud-native era.
Binary file added assets/images/posts/howto/iyanev/ms-structure.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.