Clean Architecture: Implementation Using Quarkus + Gradle Multi-Project. Part 2
As we venture deeper into our Gradle Multi-Project Clean Architecture journey with Quarkus and Java 17, we find ourselves at a pivotal juncture where our architectural components meet practical implementation. In the last few segments of our blog series, we've been focusing on fine-tuning the
entrypoint
layer, which serves as the gateway for external interactions with our application.
In this entry, we'll explore the configuration of three distinct modules within the
entrypoint
layer:
entrypoint-api
,
entrypoint-graphql
, and
entrypoint-dto
. Each module plays a unique role in shaping how external clients communicate with our microservices while adhering to the principles of Clean Architecture.
Let's dive into the specifics of each module and understand how they contribute to a well-structured, modular, and maintainable software system.
Configuring Module: RESTful API Endpoints
This module forms the gateway for external clients to interact with our microservices. By configuring it correctly, we ensure that our application's external interface adheres to the principles of Clean Architecture.
The
entrypoint/api
module serves as the conduit between external clients and the core business logic of our application. It defines REST endpoints that provide entry points to our microservices, allowing external systems to communicate with our application. Additionally, the
entrypoint/api
module depends on the
entrypoint/dto
module, which houses shared Data Transfer Objects (DTOs) to standardize API operations independently of protocols. This separation maintains a clean and organized architecture. Let's delve into the step of effectively configuring this module.
Dependency Setup
The heart of our Clean Architecture approach lies in proper module dependencies. The
entrypoint/api
module depends on three essential modules:
domain/model
,
business/usecase
, and
entrypoint/dto
. To establish these dependencies and ensure a clear boundary between layers, we use the
api
configuration in Gradle.
Here's how you can define these dependencies in the
build.gradle
file of the
entrypoint/api
module:
apply plugin: 'java-library' dependencies { api project(':domain-model') api project(':business-usecase') api project(':entrypoint-dto') implementation 'io.quarkus:quarkus-resteasy-reactive-jackson' // Additional dependencies related to the entrypoint/api module }
To implement RESTful endpoints in the
entrypoint/api
module, we harness the power of Quarkus. By applying the
java-library
plugin and utilizing Quarkus' built-in support for RESTful web services through the
io.quarkus:quarkus-resteasy-reactive-jackson
dependency, we create a robust and reactive API layer.
Configuring the
entrypoint/api
module is a pivotal step in our Clean Architecture project. By meticulously setting up dependencies on the
domain/model
and
business/usecase
modules, and leveraging Quarkus' capabilities for RESTful endpoints, we construct a solid external interface for our microservices.
Adding the
META-INF
Folder
Quarkus relies on the presence of the
META-INF
folder and its
beans.xml
file to understand how to perform dependency injection. This file serves as a configuration point that tells Quarkus which classes are eligible for injection and how to manage their lifecycles. Fortunately, adding this file is straightforward.
- Navigate to the
src/main/resources
directory within theentrypoint/api
module. - Create a folder named
META-INF
if it doesn't already exist. - Inside the
META-INF
folder, create an empty file namedbeans.xml
.
src/main/resources └── META-INF └── beans.xml
By adding this simple
beans.xml
file, you're telling Quarkus to perform dependency injection correctly within the
entrypoint/api
module.
To deep dive into RESTful Endpoints creation go to Writing rest services with RestEasy Reactive Quarkus guide.
Configuring Module: GraphQL Queries
This module empowers us to define GraphQL schemas and resolvers, offering an additional layer of interaction with our application. By configuring this module effectively, we ensure that our GraphQL API aligns seamlessly with our clean and modular architecture.
The
entrypoint/graphql
module acts as the gateway for external clients to interact with our microservices using the GraphQL query language. GraphQL empowers clients to request precisely the data they need, reducing over-fetching and under-fetching of data. In addition to the GraphQL functionality, the
entrypoint/graphql
module depends on three other modules:
domain/model
,
entrypoint/dto
, and
business/usecase
. This intricate web of dependencies facilitates a holistic approach to handling external interactions.
Dependency Setup
The
entrypoint/graphql
module depends on four critical modules:
domain/model
,
entrypoint/dto
,
business/usecase
, and the Quarkus
quarkus-smallrye-graphql
extension. To create a clear separation between the layers and ensure that the dependencies are appropriately managed, we utilize the
api
configuration in Gradle.
Here's how you can define these dependencies in the
build.gradle
file of the
entrypoint/graphql
module:
apply plugin: 'java-library' dependencies { api project(':domain-model') api project(':entrypoint-dto') api project(':business-usecase') implementation 'io.quarkus:quarkus-smallrye-graphql' // Additional dependencies related to the entrypoint/graphql module }
To implement GraphQL schemas and resolvers in the
entrypoint/graphql
module, we embrace the
io.quarkus:quarkus-smallrye-graphql
dependency. Quarkus provides native support for GraphQL, enabling us to define schema structures and business logic resolvers in a clean and efficient manner.
The
entrypoint/graphql
module configuration plays a pivotal role in our Clean Architecture project. We construct a powerful GraphQL interface that resonates with modern development practices.
Adding the
META-INF
Folder
As we did before, we'll include the
META-INF
folder and its accompanying
beans.xml
file. Quarkus relies on this configuration to understand how to perform dependency injection accurately. This simple file serves as a directive, informing Quarkus about which classes are eligible for injection and how their lifecycles should be managed.
To deep dive into GraphQL Queries creation go to SmallRye GraphQL Quarkus guide.
Configuring Module: Entrypoint DTO
Enhancing Data Transformation
This module is pivotal in facilitating seamless transformations between our core business domain models and Data Transfer Objects (DTOs). By configuring it effectively, we ensure clean and consistent data interchange while adhering to our modular architecture.
The
entrypoint/dto
module serves as a bridge between our core business domain models and external interfaces, providing a standardized way of representing data across layers. It's here that we implement the transformations from our core domain models to DTOs, enabling clear and controlled communication with external clients.
Dependency Setup
The
entrypoint/dto
module is intimately tied to the
domain/core
module, which houses our core business domain models. This means that the
entrypoint/dto
module depends solely on the
domain/core
module for its functionality. This minimalistic dependency structure ensures that transformations are isolated, keeping our architecture modular and free of unnecessary complexities.
Here's how you can define the dependency in the
build.gradle
file of the
entrypoint/
dto
module:
dependencies { implementation project(':domain-model') implementation 'io.quarkus:quarkus-jackson' }
In the
entrypoint/dto
module, we implement the necessary transformation logic between our core domain models and DTOs. Through the integration of the
io.quarkus:quarkus-jackson
dependency, we elevate our capacity to manage data serialization and deserialization effortlessly. This ensures that data passed between layers remains consistent and conforms to the expectations of external clients. By focusing on this transformation layer, we maintain a clear separation of concerns and prevent data leakage between layers.
The
entrypoint/dto
module plays a crucial role in our Clean Architecture project, enabling consistent and controlled data transformations between core domain models and external interfaces. By limiting its dependency solely to the
domain/core
module, we maintain the principles of modularity and encapsulation.
You can find the code of this section in the branch: steps/3-configuring-entrypoint-modules
