diff --git a/.github/workflows/antora.yml b/.github/workflows/antora.yml index e70fce90..133af868 100644 --- a/.github/workflows/antora.yml +++ b/.github/workflows/antora.yml @@ -8,27 +8,36 @@ on: jobs: build-and-deploy: runs-on: ubuntu-latest + permissions: + id-token: write # Required for authentication + contents: read # Required to read repository contents + pages: write # Required to deploy to GitHub Pages steps: - name: Checkout Repository - uses: actions/checkout@v2 + uses: actions/checkout@v3 - name: Set up Node.js - uses: actions/setup-node@v2 + uses: actions/setup-node@v3 with: - node-version: '14' # Use the Node.js version that matches your development environment + node-version: '18' # Use a compatible Node.js version - - name: Install Antora - run: npm install -g @antora/cli @antora/site-generator-default + - name: Install Antora Locally + run: npm install @antora/cli @antora/site-generator-default - name: Verify Antora Installation - run: npm list -g @antora/cli @antora/site-generator-default || echo "Antora packages are not installed." + run: npm list @antora/cli @antora/site-generator-default || echo "Antora packages are not installed." - name: Generate Site with Antora - run: antora --fetch --stacktrace playbook.yml + run: npx antora --fetch --stacktrace playbook.yml - - name: Deploy to GitHub Pages - uses: peaceiris/actions-gh-pages@v3 + - name: Setup Pages + uses: actions/configure-pages@v4 + + - name: Upload Antora Site to GitHub Pages + uses: actions/upload-pages-artifact@v3 with: - github_token: ${{ secrets.GITHUB_TOKEN }} - publish_dir: ./build/site # The default output directory for Antora - publish_branch: gh-pages # The target branch for GitHub Pages + path: ./build/site # The output directory for Antora + + - name: Deploy to GitHub Pages + id: deployment + uses: actions/deploy-pages@v4 \ No newline at end of file diff --git a/.github/workflows/static.yml b/.github/workflows/static.yml new file mode 100644 index 00000000..f2c9e97c --- /dev/null +++ b/.github/workflows/static.yml @@ -0,0 +1,43 @@ +# Simple workflow for deploying static content to GitHub Pages +name: Deploy static content to Pages + +on: + # Runs on pushes targeting the default branch + push: + branches: ["main"] + + # Allows you to run this workflow manually from the Actions tab + workflow_dispatch: + +# Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages +permissions: + contents: read + pages: write + id-token: write + +# Allow only one concurrent deployment, skipping runs queued between the run in-progress and latest queued. +# However, do NOT cancel in-progress runs as we want to allow these production deployments to complete. +concurrency: + group: "pages" + cancel-in-progress: false + +jobs: + # Single deploy job since we're just deploying + deploy: + environment: + name: github-pages + url: ${{ steps.deployment.outputs.page_url }} + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + - name: Setup Pages + uses: actions/configure-pages@v5 + - name: Upload artifact + uses: actions/upload-pages-artifact@v3 + with: + # Upload entire repository + path: '.' + - name: Deploy to GitHub Pages + id: deployment + uses: actions/deploy-pages@v4 diff --git a/README.adoc b/README.adoc index a57b0ca8..d16aa5f0 100644 --- a/README.adoc +++ b/README.adoc @@ -694,7 +694,7 @@ site: title: MicroProfile Tutorial url: https://microprofile.io keys: - seo_description: Comprehensive tutorial for learning Eclipse MicroProfile + seo_description: Comprehensive tutorial for learning MicroProfile seo_keywords: microprofile, java, cloud-native, microservices ---- @@ -709,7 +709,9 @@ ui: url: ./path/to/local/ui-bundle.zip snapshot: true ---- + Replease _./path/to/local/ui-bundle.zip_ with actual path to your local UI bundle under development. + This approach is useful when developing your own UI bundle or testing modifications. ### UI Bundle Development @@ -743,3 +745,65 @@ When troubleshooting UI problems: 2. Check the browser console for JavaScript errors 3. Verify that all required resources are loading correctly 4. Test with different browsers to identify browser-specific issues + +## GitHub Actions Workflow for Deployment + +The documentation site is automatically built and deployed using GitHub Actions. Below is an overview of the workflow: + +### Workflow Configuration + +The workflow is defined in `.github/workflows/antora.yml` and includes the following steps: + +1. **Checkout Repository**: + Uses the `actions/checkout@v3` action to fetch the repository. + +2. **Set up Node.js**: + Configures Node.js version `18` using `actions/setup-node@v3`. + +3. **Install Antora Locally**: + Installs Antora CLI and site generator locally: + ```bash + npm install @antora/cli @antora/site-generator-default + ``` + +4. **Verify Antora Installation**: + Ensures the required packages are installed: + ```bash + npm list @antora/cli @antora/site-generator-default || echo "Antora packages are not installed." + ``` + +5. **Generate Site with Antora**: + Builds the site using the playbook: + ```bash + npx antora --fetch --stacktrace playbook.yml + ``` + +6. **Setup Pages**: + Configures GitHub Pages using `actions/configure-pages@v4`. + +7. **Upload Site to GitHub Pages**: + Uploads the generated site artifact using `actions/upload-pages-artifact@v3`. + +8. **Deploy to GitHub Pages**: + Deploys the site using `actions/deploy-pages@v4`. + +### Permissions + +The workflow requires the following permissions: +- `id-token: write` for authentication. +- `contents: read` to access repository contents. +- `pages: write` to deploy the site to GitHub Pages. + +### Deployment URL + +After a successful deployment, the site is available at: +``` +https://.github.io// +``` +Replace `` (for e.g. microprofile) with your GitHub username and `` (for e.g. microprofile-tutorial) with the name of your repository. + +The production branch url for the MicroProfile Tutorial documentation would be: + +``` +https://microprofile.github.io/microprofile-tutorial/ +``` \ No newline at end of file diff --git a/antora-assembler.yml b/antora-assembler.yml index 5c7ecf66..1fe8f5cb 100644 --- a/antora-assembler.yml +++ b/antora-assembler.yml @@ -43,4 +43,4 @@ antora: output_format: pdf output: - dir: ./build/site + dir: ./build/site \ No newline at end of file diff --git a/chapter01/chapter01.adoc b/chapter01/chapter01.adoc deleted file mode 100644 index d35d8fcb..00000000 --- a/chapter01/chapter01.adoc +++ /dev/null @@ -1,279 +0,0 @@ -= Chapter 1: Introduction to MicroProfile - -== Introduction - -This introductory chapter provides a comprehensive overview of the MicroProfile platform, setting the stage for subsequent chapters in this tutorial. It aims to familiarize you with the fundamentals of the MicroProfile platform, its need, and benefits. Finally, we will discuss its place in the broader context of enterprise Java development. - -== Topics to be covered: -- What is MicroProfile -- Need for MicroProfile -- MicroProfile Specifications -- Current MicroProfile Implementations -- Architecture Philosophy -- Benefits of MicroProfile -- Relationship with Jakarta EE specification - -== What is MicroProfile - -link:https://microprofile.io/[MicroProfile] is an open-source specification that enhances enterprise Java technologies for microservices development. It provides a set of APIs and specifications for building modern, scalable, resilient, and efficient microservices-based applications. The primary goal of MicroProfile is to simplify the development for Java developers, enabling them to create applications optimized for cloud-native-development. - -MicroProfile was initiated in June 2016 by a collaboration of industry leaders, Java community members, and individual contributors. In the following year, the project was transitioned to the link:https://www.eclipse.org/[Eclipse Foundation] to enhance the project's openness and vendor-neutral stance. Now, MicroProfile has become a key framework for extending Java in the cloud-computing domain, offering a comprehensive suite of APIs tailored for developing microservices in a cloud-native ecosystem. - -The *MicroProfile Working Group* currently comprises of the following members: - -Committer Representative (Year 2024) - - * Emerson Castañeda - -Java user groups - - * Atlanta Java User Group (AJUG) - * Association of the German Java User Groups (iJUG) - -Corporate Members - - * Hunan AsiaInfo Anhui - * IBM - * Fujitsu - * Red Hat - * Primeton - * Payara - * Microsoft - * Tomitribe - * Oracle - -This collective effort demonstrates MicroProfile's commitment to evolving Java enterprise development for the modern cloud environment, leveraging the expertise of its community. - -== Need for MicroProfile - -The MicroProfile Specification was developed to address the following requirements: - -- *Microservices Architecture Adoption*: The industry shift towards microservices architecture has brought several advantages, including improved flexibility, scalability, and speed of deployment. However, it also introduced several new challenges for developers due to the added complexities. These include ensuring seamless integration between microservices, securing each microservice individually as well as interactions between them, managing performance and efficiency, designing microservices to be fault-tolerant and resilient to failures, ensuring data consistency across services, managing configurations across multiple environments and managing various independently deployable components. To address these challenges, MicroProfile provides a simplified and optimized set of APIs designed to build and deploy Java-based microservices applications. - -- *Limitations of Traditional Enterprise Java*: Traditional enterprise Java frameworks, like Java EE (now Jakarta EE), were often seen as too monolithic and heavyweight for microservices while evolving too slowly. It led to a demand for a more streamlined and microservices-focused framework. MicroProfile fills this gap by providing a lightweight alternative optimized for microservices development. - -- *Cloud-Native Application Development*: The rise of cloud-native applications necessitated new features such as external configuration, health checks, and fault tolerance, which existing Java standards did not adequately address. MicroProfile bridges these gaps left, making it easier for developers to create resilient, scalable, and manageable microservices for cloud-native application development using Java. - -- *Community-Driven Innovation*: The rapid pace of technological change in microservices necessitated a collaborative platform for innovation. MicroProfile, backed by community and vendor support, promotes rapid evolution to meet these demands. - -- *Vendor Neutrality and Interoperability*: There was a need for a framework that could provide standardization across different implementations and environments, ensuring compatibility and avoiding vendor lock-in. - -- *Focus on Simplicity and Productivity*: Developers needed a simple, easy-to-understand framework that increased productivity by reducing boilerplate code and focusing on essential microservice functionalities. Well-defined standards and patterns eliminate the need to reinvent the wheel, allowing developers to focus on microservices logic. - -- *Support for familiar programming model*: MicroProfile was founded with support for Jakarta JSON Processing, Jakarta JSON Binding, Jakarta RESTful Web Services, and Jakarta Contexts and Dependency Injection (CDI) to define the core programming model and accelerate adoption. - -- *Lightweight and Resilient Services*: With the microservices architecture, there's a need for frameworks that support the development of lightweight, resilient, and independently deployable services, which are essential for microservices. - -- *Rapid Adaptation to New Trends*: The technology landscape, especially around microservices, is constantly evolving. A framework like MicroProfile, which is community-driven and rapidly evolving, can adapt quickly to these changes, continually incorporating new practices and technologies, including: - ** *Streaming APIs and Reactive Programming Model*: To facilitate non-blocking communication and data processing, enhancing system responsiveness and scalability. - ** *API-First Development (Open API)*: Emphasizing the design and documentation of microservices with an API-first approach, promoting interoperability and clear service contracts. - ** *Eventual Consistency and Long Running Actions (LRA)*: Addressing the challenges of data consistency in distributed systems without compromising system performance. - -- *Enhanced Observability and Monitoring*: Microservices architectures complicate application monitoring and observability. A framework with built-in support for these capabilities simplifies the management of distributed services. - -== MicroProfile Specifications - -MicroProfile specifications are divided into two main categories: Platform and Standalone. - -:figure-caption: Figure -.MicroProfile Specifications -image::http://microprofile.io/wp-content/uploads/2023/10/microprofile_release_6.1.png[MicroProfile 6.1] - -=== MicroProfile Platform Component Specifications - -The MicroProfile Platform Specification is the core set of MicroProfile specifications designed to provide the foundational functionalities needed for microservices development. These specifications solve specific microservices challenges, including configuration, fault tolerance, health checks, metrics, and security. The table below provides a list of platform specifications of MicroProfile along with their descriptions: - -[options="header"] -|======================= -|Specification |Description -|link:https://microprofile.io/specifications/microprofile-config/[Config] | Provides an easy-to-use and flexible system for application configuration. -|link:https://microprofile.io/specifications/microprofile-fault-tolerance/[Fault Tolerance]| Implements patterns like Circuit Breaker, Bulkhead, Retry, Timeout, and Fallback for building resilient applications. -|link:https://microprofile.io/specifications/microprofile-jwt-auth/[JWT Authentication]| Defines a standard for using OpenID Connect (OIDC) based JSON Web Tokens(JWT) for role-based access control(RBAC) of microservices endpoints for secure communication. -|link:https://microprofile.io/specifications/microprofile-metrics/[Metrics] | Define custom application metrics and expose platform metrics on a standard endpoint using a standard format to external monitoring systems. -|link:https://microprofile.io/specifications/microprofile-health/[Health] | Allows applications to expose their health and readiness to perform operations to the underlying platform, which is crucial for automated recovery in cloud environments. -|link:https://microprofile.io/specifications/microprofile-open-api/[Open API] | Facilitates the generation of OpenAPI documentation for RESTful services, making API discovery and understanding easier. -|link:https://microprofile.io/specifications/microprofile-telemetry/[Telemetry]| Provides a unified set of APIs, libraries, and tools for collecting, processing, and exporting telemetry data (metrics, traces, and logs) from cloud-native applications and services. -|link:https://microprofile.io/specifications/microprofile-rest-client[Rest Client]| Defines a type-safe approach to invoke RESTful services over HTTP(S), simplifying the development of Rest clients. -| link:https://jakarta.ee/specifications/coreprofile/10/[Jakarta EE Core Profile 10] | An optimized Jakarta EE platform designed specifically for developing microservices and cloud-native Java applications with a reduced set of specifications for a lighter runtime footprint. -|======================= - -=== Standalone (Outside Umbrella) Specifications - -Standalone specifications address more advanced needs that every microservices application may not require. They allow for innovation and experimentation in areas that are evolving or where there’s a need to address niche concerns without burdening the core platform with additional complexity. The table below provides a list of standalone specifications of MicroProfile along with their descriptions: - -[options="header"] -|======================= -|Specification |Description -| link:https://microprofile.io/specifications/microprofile-context-propagation/[Context Propagation] | Defines a way to propagate context between threads and managed executor services. Ensure that the context is consistent during executing asynchronous tasks or across different services. -| link:https://microprofile.io/specifications/microprofile-graphql/[GraphQL] | Provides a layer on top of Jakarta EE that allows the creation of GraphQL services. This specification makes it easier to build APIs, enabling clients to request exactly the data they need and nothing more. -| link:https://microprofile.io/specifications/microprofile-lra/[Long Running Actions (LRA)]| Focuses on providing a model for developing services that participate in long-running processes, ensuring consistency and reliability without necessarily locking data. -| link:https://microprofile.io/specifications/microprofile-reactive-messaging/[Reactive Messaging]| Aims to facilitate building applications that communicate via reactive streams, enabling the development of event-driven, responsive, and resilient microservices. -| link:https://microprofile.io/specifications/microprofile-reactive-streams-operators/[Reactive Streams Operators]| Provides a way to process data streams in a reactive manner, allowing for non-blocking system design and improving the efficiency of data processing in microservices. -| link:https://microprofile.io/specifications/microprofile-opentracing/[Open Tracing]| Integrates distributed tracing by defining a way for services to trace requests across service boundaries, improving observability. -|======================= - -== MicroProfile Implementations -Below is the list of MicroProfile Implementations, each offering a platform for building and running microservices-based applications: - -- link:https://www.payara.fish/products/payara-micro/[Payara Micro^] -- link:https://tomee.apache.org/[Apache TomEE^] -- link:https://openliberty.io/[Open Liberty^] -- link:https://github.com/fujitsu/launcher[Launcher^] -- link:https://quarkus.io/[Quarkus^] -- link:https://www.wildfly.org/[WildFly^] -- link:https://helidon.io/[Helidon^] - -== Architecture Philosophy - -The overall goal of MicroProfile architecture is to provide a lightweight enterprise-grade framework tailored for building cloud-native applications and enabling developers to build and deploy microservices with Java easily: - -- *Simplicity*: MicroProfile APIs are designed to be simple and easy to use. They avoid unnecessary complexity and focus on providing the essential functionality for building microservices. - -- *Modularity*: Its modular approach allows developers to use only what they need, reducing the overhead typically associated with enterprise frameworks. - -- *Standards-based*: MicroProfile is based on open standards and specifications, ensuring compatibility and consistency across different implementations. - -- *Community-driven*: It encourages active participation from the Java community for continuous evolution. - -- *Vendor-Neutral*: MicroProfile is vendor-neutral. It’s supported by several industry players, ensuring that no single company controls its direction. - -- *Focus on Cloud-Native Applications*: The architecture is specifically tailored for cloud environments. MicroProfile integrates with a number of cloud-native technologies, such as Kubernetes and Istio. This makes it easy to deploy and manage MicroProfile applications in cloud environments. - -- *Reactive programming*: MicroProfile supports reactive programming, which is a style of programming that is well-suited for building microservices. Reactive applications are responsive and scalable, and they can handle high volumes of concurrent requests. - -:figure-caption: Figure -.Architecture Philosophy of MicroProfile -image::/images/figure1-2.png[Architecture Philosophy of MicroProfile] - -=== Benefits of MicroProfile -MicroProfile offers several benefits, making it a compelling choice for developing microservices, especially in Java-centric environments. These benefits include: - -- *Optimized for Microservices*: MicroProfile is designed explicitly for creating microservices, offering APIs that cater to the unique challenges of this architectural style. - -- *Cloud-Native Focus*: The framework includes features such as externalized configuration, health checks, and metrics, which are essential for building and operating cloud-native applications effectively. MicroProfile is inherently designed for cloud-native applications. - -- *Open Source and Standards-Based*: As an open-source framework based on open standards, MicroProfile facilitates interoperability and reduces the risk of vendor lock-in. - -- *Enhanced Productivity, Rapid Development and Deployment*: MicroProfile simplifies microservices development with a set of standard APIs. With its focus on simplicity and productivity, MicroProfile helps speed up the development and deployment of microservices by providing essential functionalities and reducing boilerplate code. - -- *Community-Driven Innovation*: Being community-driven, MicroProfile evolves quickly, incorporating new trends and best practices in microservices development. MicroProfile is backed by a strong Java community, ensuring continuous improvement and support. - -- *Vendor Neutrality*: Being vendor-neutral, MicroProfile is supported by a wide range of industry players, which ensures a broad choice of tools and platforms for developers. - -- *Compatibility with Jakarta EE*: MicroProfile is complementary to Jakarta EE, whether using MicroProfile implementations that support a small subset of Jakarta EE (such as Core Profile) or implementations that extend the full Jakarta EE Platform implementations with MicroProfile. - -- *Lightweight and Modular*: It provides a lightweight model compared to traditional enterprise Java frameworks. Its modularity allows developers to use only the necessary components, reducing the application's footprint and overhead. - -- *Scalability*: The framework supports the development of scalable applications, essential for microservices that handle varying loads efficiently. - -- *Enhanced Resilience*: MicroProfile includes specifications for fault tolerance patterns like retries, circuit breakers, timeouts, and bulkheads, which are crucial for building resilient services that can withstand network and service failures. - -- *Security Features*: MicroProfile's JWT Authentication provides a standardized way to secure microservices, making it easier to implement authentication and authorization. - -- *Ease of Testing*: With its lightweight nature and support for advanced features like Rest Client, MicroProfile simplifies the testing of microservices, both in isolation and in integration scenarios. - -== Relationship with Jakarta EE specification - -Jakarta EE is an open specification with more than 40 component specifications to address a wide array of needs of enterprise Java development. MicroProfile complements this by providing a baseline platform definition that optimizes enterprise Java for microservices architecture and delivers application portability across multiple compatible runtimes. Many Jakarta EE implementations that target a broad array of applications supplement Jakarta EE with MicroProfile to better support microservices. Their coexistence allows developers to harness the strength of both platforms, thereby facilitating a more versatile and adaptive approach to modern enterprise and cloud-native application development. MicroProfile strategically leverages Java EE developers' existing skill sets, enabling them to transition and adapt to microservices development with minimal learning curve. This ensures that developers can easily design and implement microservices architecture, enhancing productivity and facilitating the creation of cloud-native applications. Later in this tutorial, we will explore how MicroProfile API extends Jakarta EE’s capability to address microservices-specific challenges. - -NOTE: MicroProfile and Jakarta EE are complementary technologies. Both platforms enable developers to stay at the forefront of cloud-native application development. - -== Conclusion - -In this section, we explored the MicroProfile platform in detail, laying the foundation for understanding how it revolutionizes the development of microservices using Java. We started by defining MicroProfile, emphasizing its role as an open-source specification tailored for microservices development. Key contributions from industry leaders and community members have positioned MicroProfile as a pivotal technology in the Java ecosystem, especially for cloud-native application development. We delved into the essential specifications of MicroProfile, each playing a critical role in addressing specific challenges in microservices development, from configuration management to service resilience. As we move forward in this tutorial, we will delve deeper into each specification and discover how to implement MicroProfile in real-world Java applications effectively. - -[[glossary]] -== Glossary - -[[microservices]] -Microservices:: An architectural style for building applications as a collection of small, independent services. Each service focuses on a specific business capability and communicates with other services through well-defined APIs. - -[[apis]] -APIs (Application Programming Interfaces):: A set of definitions and protocols that specify how software components interact with each other. - -[[cloud-native-development]] -Cloud-native development:: An approach to building and running applications that are specifically designed for the cloud environment. It involves using technologies and practices that leverage the benefits of cloud platforms, such as scalability, elasticity, and pay-as-you-go pricing. - -[[eclipse-foundation-working-group]] -Eclipse Foundation Working Group:: A collaborative group of industry leaders and Java community members who actively contribute to the of development of Eclipse projects like MicroProfile within the Eclipse Foundation framework. - -[[jakarta-ee]] -Jakarta EE:: Jakarta EE (formerly Java Platform, Enterprise Edition, or Java EE) is a set of specifications, extending Java Platform, Standard Edition, or Java SE with specifications for enterprise features such as web services, database persistence, asynchronous messaging and more. - -[[external-configuration]] -External Configuration:: A technique in application development where configuration data is separated from the application code, allowing the application's behavior to be adjusted without changing the code, especially useful in cloud-native and microservices architectures. - -[[health-checks]] -Health Checks:: Mechanisms used in microservices architectures to continuously check the status of an application or service to ensure it is functioning correctly and available to users. - -[[fault-tolerance]] -Fault Tolerance:: The ability of a system to continue operating in the event of the failure of some of its components. This feature is critical for maintaining high availability and reliability in microservices architectures. - -[[vendor-neutrality]] -Vendor Neutrality:: The principle of designing software products and standards not controlled by any single vendor, promoting user interoperability and choice. - -[[interoperability]] -Interoperability:: The ability of a software to exchange and make use of information across different platforms and services. - -[[json-p]] -JSON-P (JSON Processing):: A Jakarta EE (formerly Java EE) API that enables parsing, generating, transforming, and querying JSON data. It facilitates the processing of JSON data within the Java programming environment. Currently it is known as Jakarta JSON Processing. - -[[json-b]] -JSON-B (JSON Binding):: A Jakarta EE (formerly Java EE) API for binding Java objects to JSON messages and vice versa, streamlining the serialization and deserialization process. It allows custom mappings to handle complex conversion scenarios efficiently. Currently it is known as Jakarta JSON Binding. - -[[jax-rs]] -JAX-RS (Java API for RESTful Web Services):: A Jakarta EE API for creating web services according to the REST architectural pattern in Java, using annotations to simplify development. It enables the easy creation and management of resources via standard HTTP methods. It is currently known as Jakarta RESTful Web Services. - -[[cdi]] -CDI (Contexts and Dependency Injection):: A Jakarta EE API for enterprise-grade dependency injection, offering type-safe mechanisms, context lifecycle management, and a framework for decoupling application components. It enhances modularity and facilitates the development of loosely coupled, easily testable applications. - -[[boilerplate-code]] -Boilerplate Code:: A piece of code that must be included in many places with little or no alteration. - -[[lightweight-services]] -Lightweight Services:: Services designed to consume minimal computing resources, enhancing performance and efficiency, particularly relevant in a microservices architecture. - -[[resilient-services]] -Resilient Services:: Services built to recover quickly from failures and continue operating. It is critical for maintaining the reliability of microservices-based applications. - -[[observability]] -Observability:: The ability to measure the internal state of a system by examining its outputs, crucial for understanding the performance and behavior of microservices. - -[[monitoring]] -Monitoring:: The practice of tracking and logging the performance and status of applications and infrastructure, essential for maintaining system health in microservices environments. - -[[circuit-breaker]] -Circuit Breaker:: A fault tolerance mechanism that prevents a failure in one service from causing system-wide failure, by temporarily disabling failing services. - -[[bulkhead]] -Bulkhead:: A pattern that isolates failures in one part of a system from the others, ensuring that parts of an application can continue functioning despite issues elsewhere. - -[[retry]] -Retry:: A simple fault tolerance mechanism where an operation is attempted again if it fails initially, based on predefined criteria. - -[[timeout]] -Timeout:: A mechanism to limit the time waiting for a response from a service, helping to avoid resource deadlock situations in distributed systems. - -[[fallback]] -Fallback:: A fault tolerance mechanism that provides an alternative solution or response when a primary method fails. - -[[rbac]] -Role-Based Access Control (RBAC):: A method of restricting system access to authorized users based on their roles within an organization. - -[[kubernetes]] -Kubernetes:: An open-source platform for automating deployment, scaling, and operations of application containers across clusters of hosts. - -[[istio]] -Istio:: An open platform to connect, manage, and secure microservices, providing an easy way to create a network of deployed services with load balancing, service-to-service authentication, and monitoring. - -[[reactive-programming]] -Reactive Programming:: A programming paradigm oriented around data flows and the propagation of change, enabling the development of responsive and resilient systems. - -[[distributed-tracing]] -Distributed Tracing:: A method for monitoring applications, especially those built using a microservices architecture, by tracking the flow of requests and responses across services. - -[[lra]] -Long Running Actions (LRA):: A model for managing long-duration, distributed transactions across microservices without locking resources. - -[[reactive-streams]] -Reactive Streams:: An initiative to provide a standard for asynchronous stream processing with non-blocking back pressure. diff --git a/chapter02/chapter02-00.adoc b/chapter02/chapter02-00.adoc deleted file mode 100644 index 993ada16..00000000 --- a/chapter02/chapter02-00.adoc +++ /dev/null @@ -1,224 +0,0 @@ -= Chapter 2: Getting Started with MicroProfile - -== Introduction - -In this chapter, you'll embark on your MicroProfile journey! We will guide you through creating your first microservice, equipping you with the essential understanding to leverage this robust framework for building modern, cloud-native applications. This journey begins with setting up your development environment, then diving into creating a microservice. - -== Topics Covered - -- Development Environment Setup -- Configuring Build Tools -- Initializing a New MicroProfile Project -- Choosing Right Modules for your Application -- Building a RESTful service -- Deployment -- Testing your microservices -- Exploring Further with MicroProfile - -== Development Environment Setup - -Let's begin by preparing your workspace for MicroProfile development: - -=== Java Development Kit (JDK) - -MicroProfile, a Java framework, runs on the Java Virtual Machine (JVM), making the Java Development Kit (JDK) an essential component of your development environment. - -To install JDK, follow the steps below: - -. *Download*: Visit the official link:https://openjdk.org/[OpenJDK] website and download the JDK version compatible with your operating system. - -. *Install*: Follow the installation instructions provided on this official link:https://openjdk.org/install/[OpenJDK Installation] guide. - -. *Verify*: After Installation, run the following command in your command line or terminal to verify the Installation: - -[source, bash] ----- -java -version. ----- - -You should an output similar to the one below: ----- -openjdk 17.0.10 2024-01-16 LTS -OpenJDK Runtime Environment Microsoft-8902769 (build 17.0.10+7-LTS) -OpenJDK 64-Bit Server VM Microsoft-8902769 (build 17.0.10+7-LTS, mixed mode, -sharing) ----- - -This confirms that JDK 17 has been successfully installed on your system. - -NOTE: For most MicroProfile implementations, JDK 11 or later is recommended. In -this tutorial, we will be using JDK 17. While OpenJDK is used here, other JDK -providers such as Oracle JDK, Amazon Corretto, and Azul Zulu also offer -compatible JDK distributions. - -=== Build Tools (Maven or Gradle) - -Build tools like link:https://maven.apache.org/[Apache Maven] or link:https://gradle.org/[Gradle] are commonly used for managing project dependencies and building Java applications. You can install the one that best fits your project needs. Here's a brief overview to help you decide: - -* *Apache Maven*: Known for its convention-over-configuration approach, Maven is a popular choice due to its simple project setup and extensive plugin repository. - -* *Gradle*: Offers a flexible, script-based build configuration, allowing for highly customized build processes. Gradle is renowned for its superior performance, due to its incremental builds and caching mechanisms. It's a great choice for complex projects requiring customization. - -IMPORTANT: If your existing project's build uses Maven wrapper (`mvnw`) or Gradle wrapper (`gradlew`), you don't to install any of these build tools. These wrappers help ensure a consistent build environment without requiring the build tools to be installed on your system. - -==== Installing Apache Maven - -To install Maven follow the steps below: - -. Visit the link:https://https://maven.apache.org/install.html[Installing Apache Maven] web page to download the latest version. - -. Follow the installation instructions provided on the site. - -. Verify the Maven installation by running this command in your terminal or command line. - ----- -mvn -v ----- - -You should see output similar to: - ----- -Apache Maven 3.9.6 (bc0240f3c744dd6b6ec2920b3cd08dcc295161ae) -Maven home: /usr/local/sdkman/candidates/maven/current -Java version: 17.0.10, vendor: Microsoft, runtime: /usr/lib/jvm/msopenjdk-current -Default locale: en_US, platform encoding: UTF-8 -OS name: "linux", version: "6.2.0-1019-azure", arch: "amd64", family: "unix" ----- - -After Maven is installed, you can configure the _pom.xml_ file in your project to include the MicroProfile dependencies. - -==== Gradle - -To install Gradle follow the step below: - -. Visit the link:https://gradle.org/install/[Gradle | Installation] web page to download the latest version. -. Follow the installation instructions provided on the site. -. Verify the installation by running this command in your terminal or command line. - ----- -gradle -version ----- - -You should see output similar to: - ----- -Welcome to Gradle 8.6! - -Here are the highlights of this release: - - Configurable encryption key for configuration cache - - Build init improvements - - Build authoring improvements - -For more details see https://docs.gradle.org/8.6/release-notes.html - ------------------------------------------------------------- -Gradle 8.6 ------------------------------------------------------------- - -Build time: 2024-02-02 16:47:16 UTC -Revision: d55c486870a0dc6f6278f53d21381396d0741c6e - -Kotlin: 1.9.20 -Groovy: 3.0.17 -Ant: Apache Ant(TM) version 1.10.13 compiled on January 4 2023 -JVM: 17.0.10 (Microsoft 17.0.10+7-LTS) -OS: Linux 6.2.0-1019-azure amd64 ----- - -After Gradle is installed, you can configure the _build.gradle_ file in your project to include the MicroProfile dependencies. - -Whether you opt for Maven's stability and convention or Gradle's flexibility and performance, understanding how to configure and use your chosen build tool is important for MicroProfile development. - -=== Integrated Development Environments - -Integrated Development Environments (IDEs) enhance developer productivity by providing a rich set of features and extensions such as project boostraping, dependency management, intelligent code completion, configuration assistance, test runners, build, hot deployment and debugging tools. For MicroProfile development, the choice of IDE can significantly affect your development speed and efficiency. Below is a list of popular IDEs and their key features related to Java and MicroProfile development: - -==== Eclipse for Enterprise Java and Web Developers - -_Overview_: link:https://www.eclipse.org/downloads/packages/release/2023-12/r/eclipse-ide-enterprise-java-and-web-developers[Eclipse for Enterprise Java and Web Developers] is a widely used IDE for Java development, offering extensive support for Java EE, Jakarta EE, and MicroProfile, among other technologies. - -_Getting Started_: The official Eclipse documentation containing instructions about creating Java projects - link:https://help.eclipse.org/latest/topic/org.eclipse.jdt.doc.user/gettingStarted/qs-3.htm[Creating your first Java Project] - -==== IntelliJ IDEA -_Overview_: link:https://www.jetbrains.com/idea/[IntelliJ IDEA] by JetBrains supports a wide range of programming languages and frameworks, including Java, Kotlin, and frameworks like Spring, Jakarta EE, and MicroProfile. - -_Getting Started_: Refer to this IntelliJ IDEA guide on link:https://www.jetbrains.com/help/idea/2024.1/creating-and-running-your-first-java-application.html[Creating a Java Project Using IntelliJ IDEA 2024.1]. - -==== Apache NetBeans - -_Overview_: link:https://netbeans.apache.org/front/main/[NetBeans] is an open-source IDE that supports Java development, including Java SE, Java EE, JavaFX, and more. - -_Getting Started_: Check out this link:https://netbeans.apache.org/tutorial/main/kb/docs/java/quickstart/[NetBeans Java Quick Start Tutorial] for a tutorial on creating a Java application. - -==== Visual Studio Code - -_Overview_: link:https://code.visualstudio.com/[Visual Studio Code] is a lightweight, powerful source code editor that supports Java development through extensions. - -_Getting Started_: To start with Java in VS Code, follow this link:https://code.visualstudio.com/docs/java/java-tutorial[Getting Started with Java in VS Code] documentation. - -Selecting an IDE should be based on personal preference, as the best choice varies depending on individual needs, familiarity, and the specific features that enhance your productivity. Each IDE offers unique advantages for MicroProfile development. - -=== Setting up MicroProfile Runtime - -MicroProfile applications need a runtime that supports MicroProfile specifications or a MicroProfile-compatible server to run your applications. Below are some popular options, each with unique features tailored to different needs: - -==== Open Liberty - -link:https://openliberty.io/[Open Liberty] is a flexible server framework from IBM that supports MicroProfile, allowing developers to build microservices and cloud-native applications with ease.Open Liberty is known for its dynamic updates and lightweight design, which enhances developer productivity and application performance. - -link:https://openliberty.io/start/[Downloading Open Liberty] page provides access to its latest releases and documentation to help you set up your environment. - -==== Quarkus - -link:https://quarkus.io/[Quarkus] is known for its container-first approach, offering fast startup times and low memory footprint. It aims to optimize Java for Kubernetes and cloud environments - -This link:https://quarkus.io/guides/getting-started[Getting Started with Quarkus] page will guide you through creating your first Quartus project and exploring its cloud-native capabilities. - -==== Payara Micro - -link:https://www.payara.fish/products/payara-micro/[Payara Micro] is a lightweight middleware platform suited for containerized Jakarta EE and MicroProfile applications. - -The link:https://www.payara.fish/downloads/payara-platform-community-edition/[Payara Platform Community Edition] enables easy packaging of applications into a single, runnable JAR file, simplifying deployment and scaling in cloud environments. This site about Payara Platform Community Edition offers downloads and documentation to get started. - -==== WildFly - -link:https://www.wildfly.org/[WildFly] is a flexible, lightweight, managed application runtime that offers full Jakarta EE and MicroProfile support. WildFly is designed for scalability and flexibility in both traditional and cloud-native environments. - -link:https://www.wildfly.org/downloads/[WildFly Downloads] page offers the latest versions and documentation to get you started. - -==== Helidon - -Developed by Oracle, link:https://helidon.io/[Helidon] MP implements MicroProfile specifications. It provides a familiar programming model for Jakarta EE developers and enables efficient microservice development. - -link:https://helidon.io/docs/[Helidon Documentation] provides comprehensive resources to help developers get started with the framework, understand its core concepts, and develop microservices efficiently. - -==== Apache TomEE - -link:https://tomee.apache.org/[Apache TomEE] integrates several Apache projects with Apache Tomcat to provide a Jakarta EE environment. It offers support for MicroProfile, allowing developers to build and deploy microservices using the well-known Jakarta EE technologies with additional MicroProfile capabilities. - -link:https://tomee.apache.org/download.html[TomEE Downloads] and link:https://tomee.apache.org/microprofile-6.0/javadoc/[TomEE MicroProfile Documentation] page provide the necessary resources to get started with TomEE for MicroProfile development. - -=== MicroProfile Starter -To kickstart your MicroProfile project, use the MicroProfile Starter to generate a sample project with your chosen server and specifications. This tool provides a customizable project structure and generates necessary boilerplate code and configuration. - -* Visit the link:https://start.microprofile.io/[MicroProfile] Starter page - the official website for generating the MicroProfile project templates. - -* Provide a `groupId` for your project, it's an identifier for your project and should be unique to avoid conflicts with other libraries or projects. - -TIP: Its recommended convention is to start your `groupId` with the reverse domain name of your organization (for example, `io.microprofile`). - -* Enter the 'artifactID', which is the name of your project (e.g., 'mp-ecomm-store'). - -* Select the *Java SE version* your project will use. - -* Select the *MicroProfile version* you want to use. Ideally, you should choose the latest version for the most up-to-date features but also consider the runtime’s support. - -* Select the specifications you want to include in your project. These could be Config, Fault Tolerance, JWT Auth, Metrics, Health, Open API, Open Tracing, Rest Client. Choose what is relevant to your application. - -* Click the _Download_ button. - -* Unzip the generated project and import it into your IDE. - -This completes the development environment setup. Now we are all set to begin development using MicroProfile. - -IMPORTANT: At the time of writing this tutorial, the latest MicroProfile released version was 6.1. The MicroProfile Starter does not currently support this version. Hence, we will not be using MicroProfile Starter to generate the project structure. diff --git a/chapter02/chapter02-01.adoc b/chapter02/chapter02-01.adoc deleted file mode 100644 index 1dd2dc93..00000000 --- a/chapter02/chapter02-01.adoc +++ /dev/null @@ -1,157 +0,0 @@ -== Creating a Java Project for MicroProfile Development - -=== Using Your IDE - -Most modern IDEs have built-in support for creating Java and Maven projects. Depending on your chosen IDE, look for options like "New Project", or "New Maven Project" to get started. These options typically guide you through the setup process, including specifying the project's group Id, artifact Id, and dependencies. - -=== Using Maven from Command Line (Terminal) - -For developers who prefer using the command line or for those setting up projects in environments without an IDE, Maven can generate the base structure of a Java project through its archetype mechanism. -To create a project using Maven, open your terminal or command line and run the following command: - -[source, bash] ----- -mvn archetype:generate -DgroupId=io.microprofile.tutorial -DartifactId=store --DarchetypeArtifactId=maven-archetype-quickstart -DinteractiveMode=false ----- - -The above command creates a new Maven project based on the `maven-archetype-quickstart archetype`, setting the `groupId` as `io.microprofile.tutorial` and the `artifactID` as `store`. - -Explanation: - -* `mvn archetype:generate` goal in this command initializes the project creation process and instructs Maven to generate a new project based on an archetype template. - -* `-DgroupId=io.microprofile.tutorial` Specifies the groupId for the project. The groupId is a unique identifier for your project and is usually based on the domain name of your organization in reverse. In this case, io.microprofile.tutorial represent a project related to MicroProfile tutorial. - -* `-DartifactId=store`: Sets the `artifactId` for the project. The `artifactId` is the name of the project and is used as the base name for the project's artifacts (e.g., WAR files). Here, `store` is used as the project name to indicate this project is related an e-commerce store application. - -* `-DarchetypeArtifactId=maven-archetype-quickstart`: Indicates which archetype to use for generating the project. The maven-archetype-quickstart is a basic template for a Java application, providing a simple project structure that includes a sample application (App.java) and a unit test (AppTest.java). - -* `-DinteractiveMode=false`: This option disables interactive mode, meaning Maven will not prompt you for input during the project generation process. All necessary information is provided via the command-line options, allowing the command to execute without further user interaction. - -Next, using your file explorer or command line, create the following folder structure: - -[source, plain text] ----- -. -├── pom.xml -├── readme.md -└── src - └── main - │ └── java - │ └── io - │ └── microprofile - │ └── tutorial - │ └── store - │ └── product - │ │ ├── entity - │ │ │ └── Product.java - │ │ └── service - │ │ └── ProductService.java - │ └── ProductRestApplication.java - └── test - └── java - └── io - └── microprofile - └── tutorial - └── store - └── product - └── ProductServiceTest.java ----- - -The standard location for your Java source code is this folder: ----- -src/main/java ----- - -And, the standard location for your test code is this folder: ----- -src/test/java ----- - - -You can delete `App.java` and `AppTest.java` files from the poject as these are not required in MicroProfile development. - -The heart of your Maven project is `pom.xml` (Project Object Model) file. It defines project metadata, dependencies, build configurations and plugins. - -The content for the _pom.xml_ file should look as below, ensure MicroProfile depencency is added: - -[source,xml] ----- - - - 4.0.0 - - io.microprofile.tutorial - mp-ecomm - war - 1.0-SNAPSHOT - - - - org.projectlombok - lombok - 1.18.26 - provided - - - - - - jakarta.platform - jakarta.jakartaee-api - 10.0.0 - provided - - - - - - org.eclipse.microprofile - microprofile - 6.1 - pom - provided - - - - - - org.junit.jupiter - junit-jupiter-api - 5.8.2 - test - - - - - org.junit.jupiter - junit-jupiter-engine - 5.8.2 - test - -... - ----- - -Below is the list of essential dependencies you need to add to your Maven _pom.xml_ for a MicroProfile project: - -* *Lombok Dependency* - Simplifies your model by auto-generating getters, setters, constructors, and other boilerplate code. -* *Jakarta EE API Dependency* - Provides the APIs for Jakarta EE, which are often used alongside MicroProfile for enterprise Java applications. -* *MicroProfile Dependency* - This is the core MicroProfile dependency that allows you to use MicroProfile specifications in your project. -* *JUnit Jupiter API for Writing Tests* - Essential for writing unit tests for your MicroProfile services. -* *JUnit Jupiter Engine for Running Tests* - Enables the execution of JUnit tests. - -These dependencies provide a foundation for building MicroProfile applications, including aspects like model simplification with Lombok, the application of Jakarta EE APIs for building robust enterprise applications, and testing with JUnit. Remember to adjust the versions based on your project requirements and the compatibility with your MicroProfile runtime​​. - -TIP: Execute the `$ mvn validate command`. This checks the _pom.xml_ file for correctness, ensuring that all necessary configuration is present and valid. diff --git a/chapter02/chapter02-02.adoc b/chapter02/chapter02-02.adoc deleted file mode 100644 index 38081804..00000000 --- a/chapter02/chapter02-02.adoc +++ /dev/null @@ -1,184 +0,0 @@ -== Choosing Right Modules for Your MicroProfile Application - -Choosing the right modules for your MicroProfile application is crucial for ensuring that your application is lean, maintainable, and only includes the necessary functionalities to meet its requirements. - -Before diving into MicroProfile modules, it's essential to have a clear understanding of your application's requirements. Consider aspects such as configuration needs, security, health checks, data metrics, fault tolerance, and the need for distributed tracing. Mapping out these requirements will guide you in selecting the most relevant MicroProfile specifications. MicroProfile provides a selection of APIs that you can choose from based on the specific needs of your application. However, with the variety of specifications available, it's important to understand which ones best fit your project's needs. - -This section aims to help you make informed decisions about which MicroProfile modules to use. - -=== Use the Entire MicroProfile Dependency - -If you're beginning a new MicroProfile-based project and are unsure which specifications you will need, starting with the entire MicroProfile dependency can give you immediate access to the full suite of MicroProfile APIs. This approach allows you to explore and experiment with different specifications without modifying your pom.xml to add or remove dependencies frequently. - -For projects that aim to leverage a wide range of MicroProfile specifications, including advanced features like telemetry, metrics, and fault tolerance, including the entire MicroProfile 6.1 dependency ensures that you have all the necessary APIs at your disposal. This approach simplifies dependency management, especially for complex applications. - -*Maven* - -[source, xml] ----- - - - - org.eclipse.microprofile - microprofile - 6.1 - pom - provided - ----- - -*Gradle* -[source, xml] ----- -dependencies { - compileOnly 'org.eclipse.microprofile:microprofile:6.1' -} ----- - -=== Use Individual MicroProfile Specification Dependencies - -For applications where size and startup time are critical (e.g., serverless functions, microservices with stringent resource constraints), including only the necessary MicroProfile specifications can help minimize the application's footprint. This selective approach ensures that your application includes only what it needs, potentially reducing memory usage and startup time. - -To prevent potential conflicts or security issues associated with unused dependencies, it's prudent to include only the specifications your application directly uses. This practice follows the principle of minimalism in software design, reducing the surface area for bugs and vulnerabilities. - -The list below is provided to help you select the appropriate modules for your MicroProfile application: - -* *MicroProfile Config* provides a way to fetch configurations from various sources dynamically. You should use this dependency in your microservices if they require external configuration or need to be run in different environments without requiring repackaging. - -*Maven* - -[source, xml] ----- - - org.eclipse.microprofile.config - microprofile-config-api - 3.1 - ----- - - -* *MicroProfile Health* allows you to define health endpoints easily. If you're deploying your application in a environment where the service needs to report its health status. - -*Maven* - -[source, xml] ----- - - org.eclipse.microprofile.health - microprofile-health-api - 4.0.1 - ----- - - - -* *MicroProfile Metrics* offers a way to generate various metrics from your application, which can be consumed by monitoring tools. You should use this dependency in your microservices if you need to monitor the performance of your application. - -*Maven* - -[source, xml] ----- - - org.eclipse.microprofile.metrics - microprofile-metrics-api - 5.1.0 - ----- - - -* *MicroProfile Fault Tolerance* helps applications in implementing patterns like timeout, retry, bulkhead, circuit breaker, and fallback. Applications requiring resilience and reliability, especially those facing network latency or failure in microservices environments, will benefit from it. - -[source, xml] ----- - - org.eclipse.microprofile.fault-tolerance - microprofile-fault-tolerance-api - 4.0.2 - ----- - -* *MicroProfile JWT Authentication* provides a method for using JWT tokens for securing your microservices, especially where propagation of identity and authentication information is needed across services. - -[source, xml] ----- - - org.eclipse.microprofile.jwt - microprofile-jwt-auth-api - 2.1 - ----- - - -* *MicroProfile OpenAPI* offers tools for generating OpenAPI descriptions of your endpoints automatically for documenting your REST APIs. - -*Maven* - -[source, xml] ----- - - org.eclipse.microprofile.openapi - microprofile-openapi-api - 3.1.1 - ----- - -* *MicroProfile Rest Client* simplifies calling RESTful services over HTTP for type-safe invocations of HTTP services for type-safe invocations of HTTP services. - -*Maven* - -[source, xml] ----- - - org.eclipse.microprofile.rest.client - microprofile-rest-client-api - 3.0 - ----- - -* *MicroProfile Telemetry* integrates OpenTelemetry for distributed tracing For applications that need to trace requests across microservices to diagnose and monitor. - -*Maven* - -[source, xml] ----- - - - - - io.opentelemetry - opentelemetry-bom - 1.35.0 - pom - import - - - - - - io.opentelemetry - opentelemetry-api - - - ----- - -* *Jakarta EE Core Profile* dependency provides the API set included in the Jakarta EE 10 Core Profile, which is optimized for developing microservices and cloud-native Java applications with a reduced set of specifications for a lighter runtime footprint. - -*Maven* - -[source, xml] ----- - - - - jakarta.platform - jakarta.jakartaee-api - 10.0.0 - provided - - ----- - -For rapidly evolving projects or those in the exploratory phase, starting with the full MicroProfile dependency might be advantageous. However, for production applications with well-defined requirements, opting for individual specifications can lead to more efficient and maintainable solutions. - -When choosing MicroProfile modules, start with the minimal set that meets your application's core requirements. You can always integrate additional specifications as your application evolves. This approach keeps your application lightweight and focused on its primary functionalities, improving maintainability and performance. Always consider the compatibility between different versions of MicroProfile and your runtime environment to ensure seamless integration and deployment. diff --git a/chapter02/chapter02-03.adoc b/chapter02/chapter02-03.adoc deleted file mode 100644 index 05b5edc1..00000000 --- a/chapter02/chapter02-03.adoc +++ /dev/null @@ -1,319 +0,0 @@ -== Developing a RESTful Web Service - -*Web Services* are very popular nowadays because they allow for building decoupled systems -– services can communicate with each other without the knowledge of each other’s implementation details. -There are many different ways to design and implement web services. One popular way is to use the Representational State Transfer (REST) -architecture. A Jakarta RESTful Webservice is a web service that uses the *Representational State Transfer (REST)* architecture. -This type of web service makes it easy to build modern, scalable web applications. The REST architecture is based on the principle that -all data and functionality should be accessed through a uniform interface. This makes it easy to develop, test, and deploy web -applications. - -To understand this better, let’s create a simple RESTful service to manage a list of products for our sample application, -the MicroProfile ecommerce store. This RESTful API will allow client applications to access the product information stored as -resources on the server. For example, let’s say you have a product catalog that you want to make available as a web service. -With REST, you would create a URL that represents the resources (products) in your catalog. When a client (such as a web browser) -requests this URL, the server would return a list of products in JSON format. - -=== Creating an Entity class - -An Entity class represents a specific object, in our case a product. It contains the product's details id and name, -and other properties like price, and quantity. To implement an entity class first, you need to create a `Product` class, as below: - -[source, java] ----- -// Product.java -package io.microprofile.tutorial.store.product.entity; - -import lombok.AllArgsConstructor; -import lombok.Data; - -@Data -@AllArgsConstructor -@NoArgsConstructor -public class Product { - private Long id; - private String name; - private String description; - private Double price; -} ----- - -Explanation: - -* The `Product` class is a Plain Old Java Object (POJO). It has an `id`, `name`, `description` and `price` property. The `id` property is of type `Long`, The `name` and `description` properties are of type `String`. The `price` property is of type `Double`. - -* `@Data` annotation will generate constructors, getters, and setters for all fields. By doing this, -you enable the Jackson library to convert your Java objects to JSON and vice versa. All properties must be of `Object` type as well. -Jackson cannot work with primitive types because they cannot be `null`. - -* `@AllArgsConstructor` generates a constructor with one argument for each field in the class. -This is useful for instantiating objects with all their fields initialized. - -* `@NoArgsConstructor` generates a default constructor -for the class. - -=== Creating a Resource class - -A resource class represents a collection of related resources. It includes methods for creating, updating, deleting, and retrieving -(CRUD) operations on the resources. Let us now create a `ProductResource` class with a `getProducts()` method to return a list of -`Product` objects. - -[source, java] ----- -// ProductResource.java -package io.microprofile.tutorial.store.product.resource; - -import java.util.ArrayList; -import java.util.List; - -import io.microprofile.tutorial.store.product.entity.Product; -import jakarta.enterprise.context.ApplicationScoped; -import jakarta.ws.rs.GET; -import jakarta.ws.rs.Path; -import jakarta.ws.rs.Produces; -import jakarta.ws.rs.core.MediaType; - -@Path("/products") -@ApplicationScoped -public class ProductResource { - private List products; - - public ProductResource() { - products = new ArrayList<>(); - - products.add(new Product(Long.valueOf(1L), "iPhone", "Apple iPhone 15", Double.valueOf(999.99))); - products.add(new Product(Long.valueOf(2L), "MacBook", "Apple MacBook Air", Double.valueOf(1299.99))); - } - - @GET - @Produces(MediaType.APPLICATION_JSON) - public List getProducts() { - // Return a list of products - return products; - } -} ----- - -Explanation: - -* The `ProductResource` is annotated with `@ApplicationScoped`. This will ensure that this class is available as long as the -application is running. - -* The `ProductResource` class has a `getProducts()` method, which returns a list of products. This method is annotated with the `@GET` annotation, which maps this method to the `GET` HTTP method. - -* The `@Produces` annotation tells the server that this method produces JSON content. This will return the following JSON response when we make a `GET` request to the `/api/products` endpoint. - -* RESTful web services can produce and consume many different media types, including JSON, XML, and HTML. - -* Annotations specify the media type that a method can consume or produce. For example, if a method is annotated with -`@Produces(MediaType.APPLICATION_JSON)` it can produce JSON. - -=== Creating a Resource class - -Create a class named `ProductRestApplication` as per the code below: - -[source, java] ----- -// ProductRestApplication.java -package io.microprofile.tutorial.store.product; - -import jakarta.ws.rs.ApplicationPath; -import jakarta.ws.rs.core.Application; - -@ApplicationPath("/api") -public class ProductRestApplication extends Application{ - -} ----- - -Explanation: - -* The annotation `@ApplicationPath("/api")` specifies that any RESTful resources registered within this application will be accessed -under the base path `/api`. For example, if you have a resource class named `ProductResource` mapped to the path `/products`, it would be accessible at `/api/products`. - -=== Building Your Application - -You may build the application using the following commands from your project’s root directory: - -[source, bash] ----- -$ mvn compile ----- - -The above command will compile your project’s source code. - - -[source, bash] ----- -$ mvn test ----- - -The above command will run the test using a unit testing framework. These test should not require the code to be packaged and deployed. - - -[source, bash] ----- -$mvn package ----- - -The above command will create a deployment package. - -=== Deploying your microservices - -This section guides you through deploying your newly created product microservice to a runtime environment. Below are some of the general considerations: - -==== General Considerations: -* Runtime Compatibility: Ensure your chosen runtime supports the MicroProfile version used in your project. -* Packaging: Decide on a packaging format (e.g., WAR file, Docker image). -* Configuration: Review and adjust any runtime configuration necessary for your service. -* Deployment Tools: Leverage runtime-specific tools or commands for deployment. - -==== Deployment Options -You can then deploy this application on a MicroProfile compatible server and access the web service at -`http://localhost://api/products`. Replace `` with the port number on which the web server or -application server is listening. The `` is a placeholder for the context root of the web application. -The context root is part of the URL path that identifies the base path for the application on the web server. - -Below are the steps for popular options. Specific steps will depend on your chosen runtime. - -*Open Liberty* - -Package your application as a WAR file using Maven or Gradle by adding the packaging tag in `pom.xml`. - -[source, xml] ----- -io.microprofile.tutorial -mp-ecomm-store -1.0-SNAPSHOT -war ----- - -Add a server configuration file at the location /main/liberty/config/server.xml with the content as below: - -[source, xml] ----- - - - restfulWS-3.1 - jsonb-3.0 - - - - - ----- - -Add the Open Liberty configuration in the pom.xml as below: - -[source, xml] ----- - - UTF-8 - UTF-8 - 17 - 17 - - 9080 - 9443 - mp-ecomm-store - - -Add the Open Liberty build plugin in the pom.xml as below: - - ${project.artifactId} - - - org.apache.maven.plugins - maven-war-plugin - 3.3.2 - - - io.openliberty.tools - liberty-maven-plugin - 3.8.2 - - productServer - - - - org.apache.maven.plugins - maven-failsafe-plugin - 3.0.0 - - - ${liberty.var.default.http.port} - ${liberty.var.app.context.root} - - - - - ----- - -=== Running Your Application - -Refer to your runtime’s documentation for instructions on running your MicroProfile application. -For example, Consult the Open Liberty documentation for detailed instructions: -link:https://openliberty.io/docs/latest/microprofile.html[MicroProfile - Open Liberty Docs] -Finally, use the following command from the command line or terminal to run the application on Liberty server. - -[source, bash] ----- -$ mvn liberty:run ----- - -You can also run the following command to start the liberty server in development mode. - -[source, bash] ----- -$ mvn liberty:dev ----- - -Assuming your server is running on `http://localhost:9080/`, you can access your service at: -`http://localhost:9080/mp-ecomm-store/api/products`. - -To call this RESTful web service, you can enter the URL in your browser. The response is an array of JSON objects. -Each object has an id, name, description and price property. Please note only GET methods can be tested with browsers. -The response should be - -[source, json] ----- -[{"description":"Apple iPhone 15","id":1,"name":"iPhone","price":999.99},{"description":"Apple MacBook Air","id":2,"name":"MacBook","price":1299.99}] ----- - -This uses an in-memory list; In the next chapter, in a real application you should integrate a database (via Jakarta Persistence API). We will be learning about this in the next chapter. - -*Quarkus* - -* Build your application as a native executable or Docker image. -* Run the generated executable or deploy the Docker image to a container platform. -* Refer to the Quarkus documentation for deployment guides: link:https://quarkus.io/guides/getting-started[Creating your first application - Quarkus] - -*Payara Micro* - -* Package your application as a WAR file. -* Deploy the WAR to a Payara Micro server instance. -* See the Payara Micro documentation for specific instructions: link:https://www.payara.fish/learn/getting-started-with-payara-micro/[Getting Started with Payara Micro] - -*WildFly* - -* Package your application as a WAR file. -* Deploy the WAR to a WildFly server instance. -* Refer to the WildFly documentation for deployment details: link:https://docs.wildfly.org/31/Developer_Guide.html[WildFly Developer Guide] - -*Helidon* - -* Choose between Helidon SE (native packaging) or Helidon MP (WAR packaging). -* Build your application using Gradle. -* Follow the relevant Helidon documentation for deployment steps: link:https://helidon.io/docs/v4/about/prerequisites[Helidon - Getting Started] - -*TomEE* - -* Package your application as a WAR file. -* Deploy the WAR file to the TomEE server instance. -* Refer to the TomEE documentation for instructions: link:https://tomee.apache.org/latest/examples/serverless-tomee-microprofile.html[Serverless TomEE MicroProfile] - -==== Additional Considerations: -* Containerization: Consider using containerization technologies like Docker and Kubernetes for portability and scalability. -* Cloud Deployment: Explore cloud platforms like AWS, Azure, or GCP. diff --git a/chapter02/chapter02-05.adoc b/chapter02/chapter02-05.adoc deleted file mode 100644 index 951c5737..00000000 --- a/chapter02/chapter02-05.adoc +++ /dev/null @@ -1,99 +0,0 @@ -== Package Structure - -The Table below provides an overview of the package structure and their purposes within a typical Java-based -microservices architecture. - -[options="header"] -|=== -|Package |Description - -|dto -|Data Transfer Objects (DTOs) are used to transfer data between processes, such as from your service to a REST endpoint. They often mirror entity classes but can be tailored to the needs of the client to avoid over-fetching or under-fetching data. - -|entity -|Entity classes represent the domain model and are typically mapped to database tables. These are the core classes that represent the business data and are often used with ORM tools like JPA. - -|repository -|Interfaces in this package abstract the data layer, making it easier to perform CRUD operations without dealing with database intricacies directly. This follows the Repository pattern. Data access layer, interacting with databases or other storage mechanisms (e.g., ProductRepository, CustomerRepository). - -|service -|Service classes contain the core business logic. They interact with repositories to fetch and persist data and perform operations specific to the business requirements. (e.g., ProductService, OrderService, InventoryService). - -|resource -|REST resource classes (sometimes called controllers in other frameworks) are the entry points for HTTP requests. They interact with service classes to process these requests. Interfaces defining endpoints for REST services (e.g., ProductResource, ShoppingCartResource). - -|common -|This package contains classes and interfaces that are shared across different microservices, such as utility classes, common configuration, exception handling, and security-related classes. - -|client -|For microservices to communicate with each other, they often use HTTP clients. This package contains interfaces or classes annotated for use with MicroProfile Rest Client or similar, facilitating easy communication between your services. - -|config -|Configuration classes for MicroProfile Config. - -|exception -|Custom exceptions for error handling (e.g., ProductNotFoundException, PaymentFailedException). - -|util -|Helper and utility classes. -|=== - - -*Base Package*: `io.microprofile.tutorial.store` - -[source, plaintext] -io.microprofile.tutorial.store -├── product -│ ├── resource -│ ├── config -│ ├── exception -│ ├── entity -│ ├── repository -│ ├── service -│ └── util -├── cart -│ ├── resource -│ ├── entity -│ ├── service -│ ├── repository -│ ├── client -│ ├── exception -│ └── util -├── user -│ ├── resource -│ ├── entity -│ ├── service -│ ├── repository -│ ├── exception -│ └── util -├── inventory -│ ├── resource -│ ├── entity -│ ├── service -│ ├── repository -│ ├── exception -│ └── util -├── order -│ ├── resource -│ ├── entity -│ ├── service -│ ├── repository -│ ├── exception -│ └── util -├── payment -│ ├── resource -│ ├── entity -│ ├── service -│ ├── repository -│ ├── exception -│ └── util -└── shipment - ├── resource - ├── entity - ├── service - ├── repository - ├── exception - └── util ----- - -TODO: Current package structure is just a proposal will update after completing the source code for all chapters diff --git a/chapter02/chapter02-06.adoc b/chapter02/chapter02-06.adoc deleted file mode 100644 index f0825f67..00000000 --- a/chapter02/chapter02-06.adoc +++ /dev/null @@ -1,23 +0,0 @@ -[glossary] -== Glossary - -Java Development Kit (JDK):: -A software development environment used for developing Java applications. It includes the Java Runtime Environment (JRE), an interpreter/loader (Java), a compiler (javac), an archiver (jar), a documentation generator (Javadoc), and other tools needed in Java development. - -Integrated Development Environment (IDE):: -A software application that provides comprehensive facilities to computer programmers for software development. Examples include Eclipse, IntelliJ IDEA, NetBeans, and Visual Studio Code. - -RESTful Service:: -A web service implementing REST (Representational State Transfer) principles, providing interoperability between computer systems on the internet. - -Runtime Environment:: -The environment in which programs are executed. It includes everything your application needs to run in production, such as an operating system, a runtime (like JVM for Java applications), libraries, and environment variables. - -JUnit:: -A unit testing framework for Java, used to write and run repeatable tests. - -Containerization:: -A lightweight alternative to full machine virtualization that involves encapsulating an application in a container with its own operating environment. - -Cloud Deployment:: -Deploying applications in cloud environments, leveraging cloud resources like compute instances, storage, and networking capabilities. diff --git a/chapter02/chatper02-04.adoc b/chapter02/chatper02-04.adoc deleted file mode 100644 index 66e6bbfb..00000000 --- a/chapter02/chatper02-04.adoc +++ /dev/null @@ -1,88 +0,0 @@ -== Testing your microservice - -Testing your microservice is critical for ensuring the reliability and robustness of your microservice. Maven, being a powerful project build management tool, simplifies this process by automating the test execution. -To create tests for your microservice, start by creating a class called `ProductResourceTest`, which contains unit tests for the `ProductResource` class as below: - -[source, java] ----- -// ProductResourceTest.java -package io.microprofile.tutorial.store.product.resource; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNotNull; - -import java.util.List; - -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - -import io.microprofile.tutorial.store.product.entity.Product; - -public class ProductResourceTest { - private ProductResource productResource; - - @BeforeEach - void setUp() { - productResource = new ProductResource(); - } - - @AfterEach - void tearDown() { - productResource = null; - } - - @Test - void testGetProducts() { - List products = productResource.getProducts(); - - assertNotNull(products); - assertEquals(2, products.size()); - } -} ----- - -Explanation: - -Below are the assertions to test the `getProducts()` method of `ProductService`: -* The `assertNotNull(products)`; assertion checks that products are not `null`. It ensures the method returns a list, even if it’s empty. -* The `assertEquals(2, products.size())`; assertion verifies that the list contains two products. - -==== Running Unit Tests with Maven - -To run the unit tests defined in ProductResourceTest, follow these steps: - -* Open a Terminal or Command Prompt: Navigate to the root directory of your project where the pom.xml file is located. This file contains the Maven project definition, including dependencies and test configurations. - -* Execute the Maven Test Command: Enter the following command to initiate the execution of the unit tests: - -[source, shell] ----- -$ mvn test ----- - -This command tells Maven to execute the test phase of the build lifecycle. Maven will compile the test source code, execute the test cases, and provide a summary of the test execution results. - -* Review Test Results: After running the tests, Maven displays the results in the terminal. Look for the Tests run:, Failures:, and Errors: summaries to assess the outcome. For the ProductResourceTest class, ensure that the test methods execute successfully and meet the expected assertions: - -* Addressing Failures: If any tests fail, Maven will highlight these failures in the output. Use this information to identify and fix issues in your code. Review the failing tests' output for details on the assertion failures and adjust your microservice implementation accordingly. - -* Rerun Tests: After making any necessary changes to your microservice code, rerun the tests using the mvn test command to verify that all issues have been resolved and that your microservice behaves as expected. - -By following these steps, you can leverage Maven to efficiently run and manage unit tests for your microservice, ensuring its functionality and reliability before deployment. - -== Next Steps - -Now that you have a basic MicroProfile service, consider exploring further: - -* Adding configuration with MicroProfile Config -* Implementing health checks using MicroProfile Health -* Enhancing your service with MicroProfile Fault Tolerance - -=== Resources - -* MicroProfile Official Website: https://microprofile.io/ -* MicroProfile GitHub Repository: https://github.com/eclipse/microprofile -* MicroProfile Documentation and Guides: link:https://microprofile.io/documentation/[MicroProfile Official documentation] - -After completing this chapter, you should have a basic understanding of MicroProfile and how to start building microservices with it. Continue exploring the specifications and capabilities of MicroProfile to fully leverage its power in your microservices architecture. diff --git a/chapter08/chapter08.adoc b/chapter08/chapter08.adoc deleted file mode 100644 index ea56f17b..00000000 --- a/chapter08/chapter08.adoc +++ /dev/null @@ -1,885 +0,0 @@ -= Chapter 8: MicroProfile Fault Tolerance - -In a Microservices architecture, an application consists of multiple smaller, autonomous services. This architecture enhances development flexibility, agility, and scalability but introduces new challenges, particularly in ensuring the application's reliability and managing failures. Unlike monolithic applications, where defects are localized, a single failure in one microservice can propagate across the entire application, potentially causing widespread outages. Therefore, fault tolerance is critical in a microservices architecture to ensure that failures are seamlessly isolated, managed, and recovered. - -*MicroProfile Fault Tolerance* offers strategies for building resilient and reliable microservices, ensuring service continuity and stability even during unexpected failures. - -This chapter explains how to enhance your microservices' resilience and reliability using MicroProfile Fault Tolerance capabilities and annotations. We will also demonstrate how to implement key strategies such as timeouts, retries, fallbacks, circuit breakers, and bulkheads to handle faults. By the end of the chapter, you will understand how to use these strategies to enhance the resilience of your microservices. - -== Topics to be Covered -- What is Fault Tolerance? -- Key Strategies for Enhancing Fault Tolerance -- Implementing Retry Policies and Configuration -- Avoiding and Managing Cascading Failures -- Configuring Circuit Breaker -- Using `@Asynchronous` Annotation -- Setting Timeouts -- Implementing Fallback Logic -- Isolating Resources for Fault Tolerance - -== What is Fault Tolerance? - -Fault tolerance is a system's ability to continue working correctly even in case of unexpected failures. A fault-tolerant system should be able to detect, isolate, and recover from errors without human intervention. It is critical in applications based on modern microservices architectures where individual component failures are inevitable due to network issues, resource limitations, or transient errors. - -== Key Strategies for Enhancing Fault Tolerance - -Some of the key strategies for enhancing the fault tolerance of a microservices-based application include: - -=== Asynchronous Execution - -Asynchronous execution allows operations to run in a separate thread. It means the caller does not have to wait for the operation to finish, making the application more responsive. For example, when a user searches for products in the product catalog service, the service can asynchronously fetch product recommendations from an external API while immediately returning the main search results to the user, ensuring a fast and responsive experience. - -When applied individually or in combination, these strategies form the foundation of a fault-tolerant microservices architecture. The following sections delve deeper into their implementation and best practices. - -=== Timeout - -A timeout sets a time limit for operations, preventing indefinite waits and freeing up system resources for other tasks. For instance, a timeout in payment service ensures that the application can recover gracefully if the payment processing is taking too long to respond. - -=== Retry - -A retry allows the system to automatically retry failed operations, particularly useful for handling transient errors like temporary network glitches. You can customize the retry policy with parameters such as the delay between retries and maximum retries. Adding jitter prevents synchronized retries across services. - -For example, a payment service can retry a failed payment authorization request with an external payment gateway to ensure successful transaction processing. - -=== Bulkhead - -A bulkhead isolates failures in one part of a system from other parts by segregating resources, such as thread pools, connection pools, or memory, among different microservices interactions. - -For example, in an e-commerce application, the catalog service can implement bulkheads using separate thread pools or connection pools for different upstream dependencies, such as the product database and the pricing service. If the pricing service becomes slow or unresponsive, a bulkhead prevents it from consuming all the resources of the catalog service, ensuring that requests to fetch product details from the database continue to work unaffected. - -=== Fallback - -A fallback provides a default response if an operation fails. It ensures the system continues providing a meaningful response instead of completely failing. For example, if the database fails or becomes slow in the product catalog service, the system can fetch cached product data to continue serving user requests for product listings. - -=== Circuit Breaker - -A circuit breaker stops an application from making too many unsuccessful requests to another system. If the number of failures exceeds a threshold, the circuit breaker will `open`, causing all subsequent requests to fail immediately. After a configured delay, the circuit breaker will `half-open` and allow limited requests. If those requests succeed, the circuit breaker will `close` and let all requests go through. - -image::../images/figure8-1.png[Figure 8-1: Circuit Breaker states, width=600, align="center"] - -For example, a circuit breaker can be applied to calls to an external inventory service in the Product Catalog Microservice. If the inventory service starts failing or becomes unresponsive, the circuit breaker will `open`, preventing repeated requests and reducing load. After a configured delay, the circuit breaker will `half-open` to test the availability of the inventory service with a few requests. If those succeed, the circuit breaker will `close`, resuming normal operations. - -== Fault Tolerance API - -The Fault Tolerance API equips developers with annotations to enhance the resilience of microservices against failures. It integrates seamlessly with the MicroProfile Config API, enabling the dynamic configuration of fault tolerance behaviors without modifying the application code. This section will explore using the Fault Tolerance API to build a robust, fault-tolerant microservice. - -=== Adding Dependency for Fault Tolerance API - -To use the Fault Tolerance API in your project, include the following dependency in your `pom.xml` file. Ensure you specify the version (e.g., 4.1.1) compatible with your MicroProfile runtime. - -[source,xml] ----- - - org.eclipse.microprofile.fault-tolerance - microprofile-fault-tolerance-api - 4.1.1 - ----- - -The Fault Tolerance API defines a contract for fault tolerance implementations. - -== MicroProfile Fault Tolerance Annotations - -The MicroProfile Fault Tolerance annotations provide a declarative way to implement fault-tolerant behavior in Java methods, allowing developers to handle failures gracefully with minimal code changes. - -=== List of Annotations - -|=== -| Annotation | Description -| `@Asynchronous` | Ensures that the annotated method executes in a separate thread, allowing non-blocking execution. This is useful for improving responsiveness and handling long-running tasks asynchronously. -| `@Retry` | Specifies that the annotated method should automatically retry on failure. Parameters such as `maxRetries`, `delay`, `maxDuration`, and `jitter` control retry behavior. Configurations can be externalized using MicroProfile Config. -| `@Timeout` | Specifies the maximum duration (in milliseconds) the method can execute before being aborted. If the timeout is exceeded, a `FaultToleranceException` is thrown. -| `@CircuitBreaker` | Defines a circuit breaker mechanism to prevent repeated calls to a failing method. Includes parameters like `failureRatio`, `delay`, and `requestVolumeThreshold`. -| `@Fallback` | Specifies alternative logic to execute when the primary method fails. This ensures meaningful responses and graceful degradation. -| `@Bulkhead` | Limits the number of concurrent method executions to isolate system resources and prevent cascading failures. -|=== - -=== Implementing Retry Policies and Configuration - -Retries are a fundamental fault tolerance strategy for managing transient failures such as temporary network outages or intermittent service unavailability. The `@Retry` annotation in the MicroProfile Fault Tolerance API provides a simple and effective way to implement retry policies. By customizing parameters such as the number of retries, delay between attempts, and conditions for retries, you can ensure your application responds to failures gracefully and minimizes downtime. - -==== Applying `@Retry` in `PaymentService` class -Below is an example of applying the `@Retry` annotation in a `processPayment` method within a `PaymentService` class of the MicroProfile e-commerce project: - -[source,java] ----- -package io.microprofile.tutorial.store.payment.service; - -import org.eclipse.microprofile.faulttolerance.Retry; -import jakarta.ws.rs.core.Response; -import jakarta.ws.rs.core.MediaType; - -public class PaymentService { - - @Retry( - maxRetries = 3, - delay = 2000, - jitter = 500, - retryOn = PaymentProcessingException.class, - abortOn = CriticalPaymentException.class - ) - public Response processPayment(PaymentDetails paymentDetails) throws PaymentProcessingException { - System.out.println("Processing payment for amount: " + paymentDetails.getAmount()); - - // Simulating a transient failure - if (Math.random() > 0.7) { - throw new PaymentProcessingException("Temporary payment processing failure"); - } - - return Response.ok("{\"status\":\"success\"}", MediaType.APPLICATION_JSON).build(); - } -} ----- - -==== Defining the PaymentDetails Class -To store the necessary payment information, the following `PaymentDetails` class is used. This class acts as a simple data container for payment-related details. - -[source,java] ----- - -class PaymentDetails { - private double amount; - - public double getAmount() { - return amount; - } - - public void setAmount(double amount) { - this.amount = amount; - } -} ----- -==== Creating Custom Exception Classes for Handling Failures -The `PaymentProcessingException` class represents a recoverable error, which triggers retries when thrown. -[source,java] ----- - -package io.microprofile.tutorial.store.payment.exception; - -public class PaymentProcessingException extends Exception { - public PaymentProcessingException(String message) { - super(message); - } -} - ----- -The `CriticalPaymentException` is considered a non-recoverable failure. If this exception occurs, the retry process is aborted. - -[source,java] ----- -package io.microprofile.tutorial.store.payment.exception; - -class CriticalPaymentException extends Exception { - public CriticalPaymentException(String message) { - super(message); - } -} ----- - -In this example, the `processPayment` method attempts to process a payment. If a transient failure occurs (e.g., `PaymentProcessingException`), the method retries up to three times (`maxRetries = 3`), and there is a delay of 2000 milliseconds between retries (`delay = 2000`), with a random variation of up to 500 milliseconds added to the delay (`jitter = 500`) to avoid synchronized retries (e.g. thundering herd problem). -The retries are attempted only for the exception `PaymentProcessingException` (`retryOn = PaymentProcessingException.class`) and are aborted if a `CriticalPaymentException` is encountered (`abortOn = CriticalPaymentException.class`). - -This approach helps maintain application resilience while preventing unnecessary retries that could worsen critical failures. - -==== Understanding the `@Retry` Parameters - -A retry policy specifies the conditions under which an operation should be retried. The key attributes of the `@Retry` annotation include: - -|=== -| Parameter | Description -| `maxRetries` | Specifies the maximum number of retries. -| `delay` | Sets the time (in milliseconds) to wait between retry attempts. -| `jitter` | Adds a random variation (in milliseconds) to the delay to avoid synchronized retries. -| `retryOn` | Defines the exception(s) that should trigger a retry. Defaults to all exceptions if not specified. -| `abortOn` | Specifies the exception(s) that should not trigger a retry, overriding the default retry behavior. -| `maxDuration` | Limits the total time (in milliseconds) that retries can be attempted. -|=== - -==== Externalizing Configuration with MicroProfile Config - -Retry policies can be externalized using the MicroProfile Config API. This allows you to modify the retry behavior without changing the application code. Here’s how to externalize the configuration: - -1. Add the `@Retry` annotation with minimal attributes: - -[source, java] ----- -package io.microprofile.tutorial.store.payment.service; - -import org.eclipse.microprofile.faulttolerance.Retry; -import jakarta.ws.rs.core.Response; -import jakarta.ws.rs.core.MediaType; - -public class PaymentService { - - @Retry - public Response processPayment(PaymentDetails paymentDetails) throws PaymentProcessingException { - System.out.println("Processing payment for amount: " + paymentDetails.getAmount()); - - // Simulating a transient failure - if (Math.random() > 0.7) { - throw new PaymentProcessingException("Temporary payment processing failure"); - } - - return Response.ok("{\"status\":\"success\"}", MediaType.APPLICATION_JSON).build(); - } -} ----- - -2. Define the retry policy in a configuration file (e.g., microprofile-config.properties): - -[source] ----- -io.microprofile.tutorial.store.payment.service.PaymentService/processPayment/Retry/maxRetries=3 -io.microprofile.tutorial.store.payment.service.PaymentService/processPayment/Retry/delay=2000 -io.microprofile.tutorial.store.payment.service.PaymentService/processPayment/Retry/jitter=500 ----- -In this approach, you gain flexibility to adapt retry policies based on the environment, such as increasing retry attempts in production or reducing delays during testing. - -==== Best Practices for Retry Policies - -- **Limit Retries:** Avoid setting `maxRetries` too high, as excessive retries can overwhelm the system or cause cascading failures. -- **Use Jitter:** Always configure jitter to reduce the risk of synchronized retry attempts by multiple services. -- **Abort Non-Recoverable Errors:** Use the `abortOn` parameter to exclude critical exceptions that retries cannot resolve. -- **Monitor Metrics:** Integrate with MicroProfile Metrics to track retry patterns and adjust configurations dynamically based on real-world performance. -- **Combine Strategies:** For robust error handling, use retries alongside other fault tolerance mechanisms, such as timeouts and circuit breakers. - -=== Avoiding and Managing Cascading Failures - -In a distributed microservices architecture, cascading failures occur when the failure of one service propagates to others, potentially causing widespread system outages. Such failures often result from tightly coupled services, unbounded retries, or resource exhaustion. - -==== Causes of Cascading Failures - -- **Tight Coupling:** Dependencies between services without sufficient isolation mechanisms. -- **Unbounded Retries:** Excessive retries on failing services, overwhelming resources. -- **Resource Contention:** Exhaustion of critical resources such as thread pools, memory, or database connections. -- **Lack of Fail-Safe Mechanisms:** Missing circuit breakers, bulkheads, or fallback logic. - -==== Strategies to Prevent Cascading Failures - -- Use **circuit breakers** to isolate failing services. -- Apply **bulkheads** to limit the scope of failures and resource usage. -- Set **timeouts** to prevent long-running operations from blocking resources. -- Design retries with care to avoid overwhelming the system. - -=== Configuring Circuit Breaker - -A circuit breaker is a critical fault tolerance mechanism that protects a system from repeated failures of a dependent service. It stops repeated calls to a failing service, allowing it to recover. - -==== Circuit Breaker Parameters - -|=== -| Parameter | Description -| `failureRatio` | Specifies the proportion of failed requests required to open the circuit breaker. -| `requestVolumeThreshold` | The minimum number of requests made in a rolling time window before the failure ratio is evaluated. -| `delay` | The time (in milliseconds) the circuit breaker remains open before transitioning to the "half-open" state. -| `successThreshold` | The number of consecutive successful test requests required in the "half-open" state to close the circuit breaker. -| `failOn` | Specifies the exception(s) considered failures contributing to the failure ratio. -|=== - -Below is an example of configuring a circuit breaker for a service method using the `@CircuitBreaker` annotation: - -[source,java] ----- -@CircuitBreaker( - requestVolumeThreshold = 10, - failureRatio = 0.5, - delay = 5000, - successThreshold = 2, - failOn = RuntimeException.class -) -public String getProduct(Long id) { - // Logic to call the product details service - if (Math.random() > 0.7) { - throw new RuntimeException("Simulated service failure"); - } - return productRepository.findProductById(id); -} ----- - -In the above code, the circuit breaker opens if 50% of requests fail (`failureRatio = 0.5`) after at least 10 requests (`requestVolumeThreshold = 10`). It remains open for 5 seconds (`delay = 5000`) and transitions to the "half-open" state to test recovery. Two consecutive successful requests (`successThreshold = 2`) in the "half-open" state close the circuit breaker. - -==== Externalizing Circuit Breaker Configuration - -Using MicroProfile Config, you can externalize circuit breaker parameters to make them adjustable without code changes as below: - -1. Update the `@CircuitBreaker` annotation: - -[source, java] ----- -@CircuitBreaker (failOn = RuntimeException.class) -public String getProduct(Long id) { - // Logic to call the product details service - if (Math.random() > 0.7) { - throw new RuntimeException("Simulated service failure"); - } - return productRepository.findProductById(id); -} ----- - -2. Define the configuration in *microprofile-config.properties*: - ----- -io.microprofile.tutorial.store.payment.service.ProductService/fetchProductDetails/CircuitBreaker/requestVolumeThreshold=10 -io.microprofile.tutorial.store.payment.service.ProductService/fetchProductDetails/CircuitBreaker/failureRatio=0.5 -io.microprofile.tutorial.store.payment.service.ProductService/fetchProductDetails/CircuitBreaker/delay=5000 -io.microprofile.tutorial.store.payment.service.ProductService/fetchProductDetails/CircuitBreaker/successThreshold=2 ----- - -==== Best Practices for Circuit Breaker - -- **Set Realistic Failure Ratios and Thresholds:** Tailor parameters to your services' expected load and failure behavior. -- **Monitor Metrics:** Use MicroProfile Metrics to monitor circuit breaker state transitions. -- **Combine with Other Strategies:** Use circuit breakers alongside retries and timeouts for a robust fault tolerance setup. - -=== Using `@Asynchronous` Annotation - -The *`@Asynchronous`* annotation in MicroProfile Fault Tolerance is used to enable asynchronous execution of methods. It allows operations to run in a separate thread, freeing up the main thread for other tasks. This approach enhances the application's responsiveness and scalability, particularly in high-concurrency or latency-sensitive scenarios. - -==== Why Use `@Asynchronous`? - -1. *Improved Responsiveness*: The caller does not need to wait for the method execution to complete, allowing the application to remain interactive. -2. *Non-Blocking Execution*: Long-running operations are offloaded to a separate thread, preventing bottlenecks. -3. *Scalability*: By decoupling method execution from the calling thread, you can handle higher loads without increasing thread contention. - -==== Implementation - -Below is an example of using the *`@Asynchronous`* annotation with MicroProfile Fault Tolerance: - -[source,java] ----- -package io.microprofile.tutorial.store.payment.service; - -import org.eclipse.microprofile.faulttolerance.Bulkhead; -import jakarta.enterprise.context.ApplicationScoped; -import org.eclipse.microprofile.faulttolerance.Asynchronous; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.CompletionStage; - -@ApplicationScoped -public class PaymentService { - - private static final int SIMULATED_DELAY_MS = 2000; - - /** - * Processes payments asynchronously - * - * @return A CompletionStage with the result of the operation. - */ - @Asynchronous - public CompletionStage processPayment() { - simulateDelay(); - return CompletableFuture.completedFuture("Payment processed asynchronously."); - } - - /** - * Simulates a delay in processing - */ - private void simulateDelay() { - try { - Thread.sleep(SIMULATED_DELAY_MS); // Simulating delay - } catch (InterruptedException e) { - Thread.currentThread().interrupt(); - throw new RuntimeException("Error during simulated delay", e); - } - } -} ----- - -==== Externalizing Timeout Configuration - -Timeout values can be externalized using the MicroProfile Config API, allowing flexibility to adjust values without modifying code. Here’s how: -1. Define the @Timeout annotation without specifying the value: - -[source, java] ----- -@Timeout -public String fetchData() { - // Logic -} ----- - -2. Configure the timeout in *microprofile-config.properties*: - -[source] ----- -io.microprofile.tutorial.store.payment.service.ProductService/fetchData/Timeout/value=1500 ----- - -==== Best Practices for Using @Asynchronous - -- *Use `CompletableStage` or `Future`*: Return types like `CompletableStage` allow asynchronous methods to integrate seamlessly with other asynchronous workflows. - -==== Asynchronous Execution in Fault Tolerance Strategies - -When used with other fault tolerance strategies, *`@Asynchronous`* provides a powerful mechanism to handle faults without impacting the system's responsiveness: - -1. *Asynchronous with Bulkhead*: - - Isolates resources while maintaining non-blocking execution. - - Handles concurrent requests efficiently using thread pools. - -2. *Asynchronous with Circuit Breaker*: - - Prevents system overload during failures by breaking the circuit for failing asynchronous methods. - - The circuit breaker's delay allows recovery while new threads are available for other tasks. - -=== Setting Timeouts - -Timeouts are an essential fault tolerance strategy to prevent long-running operations from consuming resources indefinitely. Slow or unresponsive services can degrade overall system performance and reliability in a microservices architecture. The `@Timeout` annotation provided by MicroProfile Fault Tolerance allows you to define a maximum duration for a method to complete, ensuring that system resources remain available for other tasks. - -==== Why Use Timeouts? - -In distributed systems, slow responses from downstream services can cascade through the system, leading to resource contention and degraded performance. Timeouts allow you to: -- Abort operations that exceed acceptable time limits. -- Free system resources for other operations. -- Trigger alternative strategies, such as fallbacks, to maintain functionality. - -[source,java] ----- -package io.microprofile.tutorial.store.payment.service; - -import io.microprofile.tutorial.store.payment.entity.PaymentDetails; -import io.microprofile.tutorial.store.payment.exception.PaymentProcessingException; - -import jakarta.enterprise.context.ApplicationScoped; -import jakarta.inject.Inject; - -import java.util.concurrent.CompletionStage; -import java.util.concurrent.CompletableFuture; -import java.util.logging.Logger; - -import org.eclipse.microprofile.faulttolerance.Asynchronous; -import org.eclipse.microprofile.faulttolerance.Timeout; - -@ApplicationScoped -public class PaymentService { - - private static final int TIMEOUT_MS = 1000; - private static final double FAILURE_THRESHOLD = 0.7; - - @Inject - private Logger logger; - - /** - * Processes payments asynchronously with a timeout. - * - * @param paymentDetails the payment details - * @return a CompletionStage with the result of the operation - */ - @Asynchronous - @Timeout(TIMEOUT_MS) - public CompletionStage processPayment(PaymentDetails paymentDetails) { - return CompletableFuture.supplyAsync(() -> { - simulateDelay(); - logger.info("Processing payment for amount: " + paymentDetails.getAmount()); - - if (Math.random() > FAILURE_THRESHOLD) { - throw new PaymentProcessingException("Temporary payment processing failure"); - } - - return "{\"status\":\"success\", \"message\":\"Payment processed successfully.\"}"; - }).exceptionally(ex -> { - logger.warning("Payment processing failed: " + ex.getMessage()); - return "{\"status\":\"failure\", \"message\":\"Payment failed due to a temporary issue.\"}"; - }); - } - - /** - * Simulates a delay in processing. - */ - private void simulateDelay() { - try { - Thread.sleep(2000); // Simulating delay - } catch (InterruptedException e) { - Thread.currentThread().interrupt(); - logger.severe("Error during simulated delay: " + e.getMessage()); - throw new RuntimeException("Error during simulated delay", e); - } - } -} ----- - -In this example, the `@Timeout(1000)` annotation specifies that the `processPayment` method must complete within 1000 milliseconds (1 second). If the execution exceeds this time, a `TimeoutException` will be thrown, and the process will terminate. `@Asynchronous` ensures non-blocking execution by making the method run in a separate thread. To explore the benefits of asynchronous programming with MicroProfile Fault Tolerance, the following resources provide valuable insights and real-world examples: - -- link:https://openliberty.io/blog/2020/06/04/asynchronous-programming-microprofile-fault-tolerance.html[Asynchronous Programming with MicroProfile Fault Tolerance (Part 1)] -- link:https://openliberty.io/blog/2020/06/05/asynchronous-programming-microprofile-fault-tolerance-part-2.html[Asynchronous Programming with MicroProfile Fault Tolerance (Part 2)] - -These articles explain how asynchronous execution enhances system responsiveness, reduces blocking, and ensures better resource utilization in MicroProfile applications. - -==== Best Practices for Timeouts - -- **Align Timeouts with SLAs:** Ensure timeout values align with service-level agreements and user expectations. -- **Monitor Performance:** Use MicroProfile Metrics to monitor execution times and identify operations requiring optimized timeout values. -- **Combine with Fallbacks:** Always pair timeouts with fallback logic to provide a reliable response in case of delays. -- **Avoid Overly Short Timeouts:** Overly aggressive timeout settings may cause unnecessary failures, particularly in high-latency environments. -- **Combine Timeout with Asynchronous:** Use timeout together with asynchronous to improve responsiveness and prevent blocking the calling thread. This approach ensures better resource utilization and system scalability during long-running operations. - -=== Implementing Fallbacks - -Fallbacks provide a default response when an operation fails. They ensure the system continues to function, even if the primary operation cannot complete successfully. The `@Fallback` annotation in MicroProfile Fault Tolerance allows developers to define fallback logic for a method, ensuring graceful degradation. - -==== Why Use Fallbacks? - -Fallbacks help to: -- Maintain system availability during failures. -- Provide a meaningful response to users instead of complete failure. -- Improve user experience by minimizing disruptions. - -[source,java] ----- -import org.eclipse.microprofile.faulttolerance.Fallback; -import jakarta.ws.rs.core.Response; - -public class PaymentService { - - @Fallback(fallbackMethod = "fallbackProcessPayment") - public Response processPayment(PaymentDetails paymentDetails) { - // Simulate a failure - throw new RuntimeException("Service Unavailable"); - } - - public Response fallbackProcessPayment(PaymentDetails paymentDetails) { - return Response.ok("{\"status\":\"failed\", \"message\":\"Payment service is currently unavailable.\"}").build(); - } -} ----- - -In this example: -- The `@Fallback` annotation specifies that if the `processPayment` method fails, the `fallbackProcessPayment` method will be executed. -- The fallback method provides a meaningful response, ensuring the user is informed of the service unavailability. - -==== Using Fallback Handlers - -A fallback handler class can implement the `FallbackHandler` interface, allowing for reusable fallback logic across multiple methods. - -[source,java] ----- -import org.eclipse.microprofile.faulttolerance.Fallback; -import org.eclipse.microprofile.faulttolerance.FallbackHandler; -import org.eclipse.microprofile.faulttolerance.ExecutionContext; - - -public class ProductService { - - @Fallback(FallbackHandlerImpl.class) - public Product getProduct(Long id) { - // Logic to call the product details service - if (Math.random() > 0.7) { - throw new RuntimeException("Simulated service failure"); - } - - return productRepository.findProductById(id); - } -} - -public class FallbackHandlerImpl implements FallbackHandler { - @Override - public String handle(ExecutionContext context) { - return "Fallback response for product details."; - } -} ----- - -==== Combining Fallbacks with Other Fault Tolerance Strategies - -Fallback logic can be combined with other fault tolerance mechanisms to create a robust strategy: -- **Timeout with Fallback:** Ensure operations terminate within a specific time and provide a fallback if they fail. - -Example: - -[source,java] ----- - -import org.eclipse.microprofile.faulttolerance.Fallback; -import org.eclipse.microprofile.faulttolerance.Timeout; - -import jakarta.enterprise.context.RequestScoped; - -import io.microprofile.tutorial.store.product.cache.ProductCache; -import io.microprofile.tutorial.store.product.entity.Product; - -@RequestScoped -public class ProductService { - - @Inject - private ProductRepository productRepository; // Access to the database - - @Inject - private ProductCache productCache; // Cache mechanism - - /** - * Retrieves a list of products. If the operation takes longer than 2 seconds, - * fallback to cached data. - */ - @Timeout(2000) // Set timeout to 2 seconds - @Fallback(fallbackMethod = "getProductsFromCache") // Fallback method - public List getProducts() { - if (Math.random() > 0.7) { - throw new RuntimeException("Simulated service failure"); - } - // database call - return productRepository.findAllProducts(); - } - - /** - * Fallback method to retrieve products from the cache. - */ - public List getProductsFromCache() { - System.out.println("Fetching products from cache..."); - return productCache.getAll().stream() - .map(obj -> (Product) obj) - .collect(Collectors.toList()); - } -} ----- - -This example demonstrates the use of MicroProfile Fault Tolerance annotations `@Timeout` and `@Fallback` to enhance the resilience of the `ProductService`. When `getProducts()` method is invoked, the application tries to retrieve product data from the database using `productRepository.findAllProducts()`. The `@Timeout(2000)` annotation ensures that this operation completes within 2 seconds. If the query executes successfully within this time, the method returns the product list as expected. However, if the execution time exceeds the timeout limit, a `TimeoutException` is triggered. Additionally, if an exception occurs within the time limit, the method also fails. To handle such failures gracefully, the `@Fallback` annotation specifies `getProductsFromCache()` as an alternative method. When a timeout or exception occurs, the fallback method is invoked, fetching product data from the cache instead of the database. This approach guarantees service availability and ensures a seamless user experience, even in scenarios where the database is slow or temporarily unavailable. For improved scalability and performance, `@Asynchronous` can be combined with `@Timeout` and `@Fallback`. This allows the method to execute in a non-blocking manner, freeing up system resources and enabling parallel processing of multiple requests. By utilizing asynchronous execution, the application can handle high loads efficiently while maintaining fault tolerance. - -==== Externalizing `@Timeout` Configuration using MicroProfile Config - -To externalize the @Timeout configuration using MicroProfile Config, you can replace the hardcoded timeout value with a configurable property. This allows us to modify the timeout dynamically without changing the source code. - -1. Define a Configurable Property: Use `@ConfigProperty` to inject the timeout value. - -[source, java] ----- - -// ... -@RequestScoped -public class ProductService { - - @Inject - private ProductRepository productRepository; // Access to the database - - @Inject - private ProductCache productCache; // Cache mechanism - - // Inject the timeout value from MicroProfile Config - @Inject - @ConfigProperty(name = "product.service.timeout", defaultValue = "2000") - private long timeoutValue; - - // ... ----- - -2. Use the Configured Value in @Timeout Annotation: Define a getter method and using it in the annotation. - -[source, java] ----- - ... - /** - * Provide the timeout value dynamically using a method reference. - */ - @Timeout(value = getTimeout()) // Use method reference to fetch dynamic value - public long getTimeout() { - return timeoutValue; - } ----- - -3. Define the Configuration Property: Configure the timeout in *microprofile-config.properties*: - -[source] ----- -io.microprofile.tutorial.store.product.service.ProductService.timeout=3000 ----- - -This sets the timeout to 3000 milliseconds (3 seconds) instead of the default 2000 making your application more configurable and adaptable without code changes. - -==== Best Practices for Fallbacks - -- **Keep Fallbacks Lightweight:** Ensure fallback logic is simple and reliable, avoiding dependencies on other potentially failing services. -- **Provide Meaningful Responses:** The fallback response should maintain a reasonable user experience, even if it cannot replicate full functionality. -- **Monitor Fallback Usage:** Use metrics to track the frequency of fallback execution, which can indicate service health and the need for improvements. -- **Plan for Degraded Functionality:** Ensure the fallback behavior aligns with business priorities and provides the most critical features. - -=== Combining Fault Tolerance Strategies - -Combining fault tolerance strategies, such as `@Timeout`, `@Fallback`, `@CircuitBreaker`, and `@Retry`, ensures resilience and efficient resource usage. Externalize configurations with MicroProfile Config for flexibility across environments. - -=== Isolating Resources for Fault Tolerance - -Resource isolation is a key principle in building resilient microservices. By isolating resources, you prevent failures in one part of the system from spreading and affecting others. MicroProfile Fault Tolerance provides features like bulkheads to achieve resource isolation and ensure critical components remain functional, even when others fail. - -==== Why Resource Isolation Matters - -In a distributed system, shared resources like thread pools, database connections, and network bandwidth can quickly become bottlenecks if not adequately managed. Resource isolation ensures: -- Failures in one service do not deplete resources for other services. -- Critical operations remain functional even under load or failure conditions. -- Better predictability and control over system behavior. - -==== Using Bulkheads to Isolate Resources - -Bulkheads are a common pattern for isolating resources by dividing a system into separate pools or partitions. This ensures that a failure in one area does not impact others. The MicroProfile Fault Tolerance standard provides the `@Bulkhead` annotation to implement this pattern. - -==== Bulkhead Types - -MicroProfile supports two types of bulkheads: - -- **Semaphore-Style Bulkhead:** Limits the number of concurrent requests. -- **Thread Pool-Style Bulkhead:** Runs a maximum number of requests on a thread pool to isolate operations. - -===== Semaphore-Style Bulkhead - -The semaphore-style bulkhead pattern limits the number of concurrent requests that can be processed by a service or method at any given time. Any additional requests are immediately rejected when the specified concurrency limit is reached. This approach prevents resource contention and protects the system from being overwhelmed during high traffic or failure scenarios. - -[source,java] ----- -package io.microprofile.tutorial.store.payment.service; - -import org.eclipse.microprofile.faulttolerance.Bulkhead; -import org.eclipse.microprofile.faulttolerance.Asynchronous; -import jakarta.enterprise.context.ApplicationScoped; -import jakarta.inject.Inject; - -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.CompletionStage; -import java.util.logging.Logger; - -@ApplicationScoped -public class PaymentService { - - @Inject - private Logger logger; - - @Inject - @ConfigProperty(name = "payment.simulatedDelay", defaultValue = "1000") - private int simulatedDelay; - - @Inject - @ConfigProperty(name = "payment.bulkhead.value", defaultValue = "5") - private int bulkheadValue; - - /** - * Processes payment transactions with limited concurrency to prevent - * system overload and ensure stability during high traffic. - * - * The @Bulkhead annotation ensures that only a limited number of - * concurrent requests can access this method. - * The @Asynchronous annotation enables the use of the thread pool - * style bulkhead for non-blocking execution. - * - * @return A success message indicating the processing status. - */ - @Asynchronous - @Bulkhead(value = bulkheadValue) - public CompletionStage processPayment() { - logger.info("Starting payment processing..."); - simulateDelay(); - logger.info("Payment processing completed."); - return CompletableFuture.completedFuture("Payment processed asynchronously."); - } - - /** - * Simulates a delay in processing. - */ - private void simulateDelay() { - try { - Thread.sleep(simulatedDelay); // Simulating delay - } catch (InterruptedException e) { - Thread.currentThread().interrupt(); - logger.severe("Error during simulated delay: " + e.getMessage()); - throw new RuntimeException("Error during simulated delay", e); - } - } -} ----- - -In this example: -- The method allows up to 5 concurrent invocations (`value = 5`). -- Any additional requests are rejected to prevent overload, ensuring system stability. - -===== Thread Pool-Style Bulkhead - -The thread-pool-style bulkhead pattern leverages a thread pool to achieve resource isolation. Incoming requests are placed into a queue when the maximum allowed number of threads are in use. Queued requests are executed as threads become available. This design helps manage resource contention effectively. - -[source,java] ----- -package io.microprofile.tutorial.store.payment.service; - -import org.eclipse.microprofile.faulttolerance.Bulkhead; -import jakarta.enterprise.context.ApplicationScoped; - -import org.eclipse.microprofile.faulttolerance.Asynchronous; - -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.CompletionStage; - -@ApplicationScoped -public class PaymentService { - - private static final Logger logger = LoggerFactory.getLogger(PaymentService.class); - - /** - * Processes payment transactions with limited concurrency using a thread pool - * to prevent system overload and ensure stability during high traffic. - * - * The @Bulkhead annotation ensures that only a limited number of concurrent - * requests (5 in this case) can access this method, and the @Asynchronous - * annotation allows the use of the thread pool style bulkhead. - */ - @Bulkhead(value = 5, waitingTaskQueue = 10) - @Asynchronous - public CompletionStage processPayment() { - return CompletableFuture.runAsync(() -> { - simulateDelay(); - System.out.println("Payment processed with limited concurrency."); - }).thenRun(() -> logger.info("Payment processed with limited concurrency.")); - } - - private void simulateDelay() { - try { - Thread.sleep(1000); // Simulating a delay - } catch (InterruptedException e) { - Thread.currentThread().interrupt(); - throw new RuntimeException("Error during payment processing simulation", e); - } - } -} ----- - -In this example, The method uses up to 5 concurrent threads (`value = 5`) from a thread pool and a queue of up to 10 tasks (`waitingTaskQueue = 10`).This configuration prevents failures in one operation from depleting shared resources. - -==== Externalizing Bulkhead Configuration - -Bulkhead resource limits can be externalized using MicroProfile Config to allow runtime adjustments. For example: - -Annotate the method without specific values: - -[source,java] ----- - @Asynchronous - @Bulkhead - public CompletionStage processPayment() { - logger.info("Starting payment processing..."); - simulatePaymentProcessing(); - logger.info("Payment processing completed."); - return CompletableFuture.completedFuture("Payment processed successfully with an isolated thread pool."); - } ----- - -Define bulkhead parameters in `microprofile-config.properties`: - -[source,properties] ----- -com.example.Service/dynamicBulkheadOperation/Bulkhead/value=5 -com.example.Service/dynamicBulkheadOperation/Bulkhead/waitingTaskQueue=10 ----- - -==== Best Practices for Resource Isolation - -- **Isolate Critical Resources:** Use bulkheads for high-priority operations, such as authentication, to ensure they are not impacted by failures elsewhere. -- **Monitor Usage:** Track bulkhead metrics using MicroProfile Metrics to identify bottlenecks and adjust limits. -- **Plan for Scaling:** Test bulkhead configurations under various load conditions to ensure scalability. -- **Combine with Graceful Degradation:** Pair bulkheads with fallbacks to handle rejected requests gracefully. - -By effectively isolating resources, you can ensure that your microservices remain reliable and resilient, even in the face of unexpected failures or high demand. This approach not only protects critical operations but also improves overall system stability. - -== Summary - -This chapter explored the MicroProfile Fault Tolerance API and essential fault tolerance strategies: - -- **Retries:** Automatically reattempt failed operations for transient errors. -- **Timeouts:** Define maximum execution times for operations to avoid resource blocking. -- **Circuit Breakers:** Prevent repeated calls to failing services and allow graceful recovery. -- **Bulkheads:** Limit concurrent operations and isolate resource usage. -- **Fallbacks:** Provide meaningful responses during failures. - -By leveraging these strategies and combining them effectively, you can design resilient microservices that gracefully handle failures, minimize disruptions, and ensure a seamless user experience. diff --git a/chapter10/index.adoc b/chapter10/index.adoc deleted file mode 100644 index ae8ef1ed..00000000 --- a/chapter10/index.adoc +++ /dev/null @@ -1,487 +0,0 @@ -== Chapter 10: JWT Authentication - -In modern microservices architectures, where services are distributed and stateless, -securing communications between clients and services and between individual services is critical. *JSON Web Token (JWT)* provides a lightweight, self-contained, and efficient user authentication and authorization mechanism, enabling scalable and secure identity propagation across distributed systems. - -*MicroProfile JWT* is a specification that standardizes JWT-based authentication and authorization for Java microservices. Leveraging the JWT open standard https://datatracker.ietf.org/doc/html/rfc7519[RFC 7519] enables services to securely extract and validate claims such as identity, roles, and permissions. - -MicroProfile JWT allows developers to build secure, interoperable, and portable microservices. It supports *role-based access control (RBAC)*, simplifies identity management in stateless services, and avoids vendor lock-in by adhering to open specifications. - -=== Topics to be covered: -- Introduction to JWT Authentication -- Structure of JWT -- Benefits of JWT in Microservices -- Setting up MicroProfile JWT -- Configuring MicroProfile JWT -- Use cases for JSON Web Tokens -- Request Flow in MicroProfile JWT -- Role-Based Access Control (RBAC) -- Setting Token Expiry Times for Security -- Error Handling -- Best Practices for JWT Authentication -- Security Best Practices for Microservices -- Conclusion - -=== Introduction to JWT Authentication - -This section will explore JSON Web Tokens, how they work, and why they are foundational to implementing stateless authentication and authorization in microservices-based systems. - -==== What is a JSON Web Token (JWT)? - -A **JSON Web Token (JWT)** (see https://jwt.io/[JWT.io]), as defined in https://datatracker.ietf.org/doc/html/rfc7519[RFC 7519], is an open standard for securely transmitting information (claims) between parties as a JSON object. JWTs are digitally signed, ensuring their integrity and authenticity. - -=== Structure of a JWT - -A JWT consists of three Base64 encoded parts, separated by dots (+.+): - -[source] ----- -
.. ----- - -- *Header* - It contains metadata about the token, such as token type (type: “JWT”) and signing algorithm ( alg: “RS256” for RSA-SHA256). - -[source, json] ----- -{ - "alg": "RS256", - "typ": "JWT" -} ----- -- *Payload* - It contains claims which are key-value pairs representing data about the user, such as roles and expiration. -Example of claims in a JWT payload: - -[source, json] ----- -{ - "iss": "https://io.microprofile.com/issuer", - "sub": "user1", - "exp": 1735689600, - "iat": 1735686000, - "aud": "my-audience", - "groups": ["user", "admin"] -} ----- - -- *Signature* — A digital signature that verifies the token’s integrity by combining the encoded header, payload, and private key. - -Example JWT Token: -[source] ----- -eyJhbGciOiJSU0ExXzUiLCJlbmMiOiJBMTI4Q0JDLUhTMjU2In0. -QR1Owv2ug2WyPBnbQrRARTeEk9kDO2w8qDcjiHnSJflSdv1iNqhWXaKH4MqAkQtM -oNfABIPJaZm0HaA415sv3aeuBWnD8J-Ui7Ah6cWafs3ZwwFKDFUUsWHSK-IPKxLG -TkND09XyjORj_CHAgOPJ-Sd8ONQRnJvWn_hXV1BNMHzUjPyYwEsRhDhzjAD26ima -sOTsgruobpYGoQcXUwFDn7moXPRfDE8-NoQX7N7ZYMmpUDkR-Cx9obNGwJQ3nM52 -YCitxoQVPzjbl7WBuB7AohdBoZOdZ24WlN1lVIeh8v1K4krB8xgKvRU8kgFrEn_a -1rZgN5TiysnmzTROF869lQ. -AxY8DCtDaGlsbGljb3RoZQ. -MKOle7UQrG6nSxTLX6Mqwt0orbHvAKeWnDYvpIAeZ72deHxz3roJDXQyhxx0wKaM -HDjUEOKIwrtkHthpqEanSBNYHZgmNOV7sln1Eu9g3J8. -fiK51VwhsxJ-siBMR-YFiA ----- - -==== Types of Claims in MicroProfile JWT - -Claims in JWTs can be categorized into two types: - -===== Standard Claims - -These are predefined claims with specific meanings, as defined by the JWT specification. Some commonly used standard claims include: - -|=== -|*Claim*|*Description*|*Example* - -|`iss`|Issuer, the entity that issued the JWT (e.g., an authentication server)|`"iss": "https://io.microprofile.com/issuer"` -|`sub`|Subject, the principal (user or service) that the JWT is about.|`"sub": "user1"` -|`aud`|Audience, the intended recipients of the token (e.g., specific microservices).|`"aud": "order-service"` -|`exp`|Expiration time|`"exp": 1735689600` -|`nbf`|not before|`"nbf": 1735686000` -|`iat`|issued at, when that token was issued.|`"iat": 1735686000` -|`jti`|Unique JWT token identifier|`"jti": "a1b2c3d4"` -|`groups`|Groups, list of roles or users allowed to access the resource |`["user", "admin"]` -|=== - -===== Custom Claims - -These are application-specific claims that provide additional information about the user or entity. They can extend authorization logic with application-specific claims (e.g., `department`, `region`). Custom claims are not part of the JWT specification but often include domain-specific data, such as user preferences, tenant IDs, or other metadata. MicroProfile JWT allows developers to access these claims programmatically. - -=== Use cases for JSON Web Tokens - -JWTs are versatile tokens commonly used in modern applications for authentication, where they verify the identity of a user or service; for authorization, where they grant access to resources based on roles or permissions; and for information exchange, where they securely transmit data between parties. - -Below are key scenarios where JWTs shine in microservices environments: - -==== Authentication - -JWTs enable stateless authentication in distributed systems. When a user logs in, an authentication service issues a JWT containing claims like sub (user ID) and exp (expiration time). The client sends this token in the `Authorization: Bearer` header of subsequent requests, allowing microservices to verify the user’s identity without requiring repeated authentication. - -For example, a user authenticates with an Auth Service and receives a JWT. This JWT token grants access to other services, such as a product catalog or order management system, without re-authentication. - -==== Authorization (Role-Based Access Control) - -JWTs are also used for authorization, enabling fine-grained access control based on user roles or permissions. The JWT payload typically includes a group or role claim specifying the user’s roles or permissions. For example, a user with the admin role might be allowed to access all resources while a user with the user role might only have access to specific resources. - -MicroProfile JWT integrates seamlessly with Jakarta EE’s `@RolesAllowed` annotation, making it easy to enforce role-based access control (RBAC) in microservices. Role mapping can be configured in _microprofile-config.properties_: - -[source] ----- -mp.jwt.verify.roles=groups ----- - -==== Claims-based identity - -JWTs are often used to represent claims-based identity, where the JWT contains claims representing the user’s identity, such as their name, email address, or other attributes. Applications can use these claims to identify the user and personalize their experience. - -For example, an application might use the email claim to look up the user’s profile information in a database or -display the user’s name on a welcome page using the name claim. - -==== Information Exchange - -JWTs can securely exchange information between parties. The token payload can include custom claims representing the data being exchanged, such as an order ID or user ID. This makes JWTs useful in scenarios like Single Sign-On (SSO) systems, where information needs to be shared across multiple services. - -For example, a JWT might contain an `order_id` claim and a `user_id` claim, which an order management service can use to retrieve and display the user’s order details. - -==== Federation & Single Sign-On (SSO) - -JWTs facilitate identity federation by allowing integration of multiple trusted identity providers (e.g., Active Directory, LDAP) to provide a single sign-on (SSO) experience. In this case, the JWT contains claims representing the user’s identity, which applications can use to identify the user and retrieve their profile information. - -For example, an enterprise SSO system can issue a JWT that grants access to HR, Payroll, and CRM microservices. MicroProfile JWT validates the token’s iss (issuer) and aud (audience) to enforce trust boundaries. - -=== Benefits of using JWT in Microservices - -JWTs are widely used in microservices for the following reasons: - -==== Statelessness & Scalability - -JWTs eliminate the need for centralized session storage. Each token is self-contained, embedding all necessary user claims (e.g., roles, permissions) in its payload. - -Independent Validation: Microservices validate JWTs locally using public keys, avoiding calls to a central authority. This reduces latency and scales horizontally. - -Example: -A payment service validates a JWT’s signature without querying an authentication server. + - -==== Interoperability - -Open Standards: JWTs adhere to RFC 7519, ensuring compatibility across platforms (Java, .NET, Node.js) and frameworks (Spring Boot, Quarkus). - -MicroProfile Integration: MicroProfile JWT standardizes validation and claim extraction, enabling seamless interoperability across Java microservices. - -==== Fine-Grained Authorization - -Role-Based Access Control (RBAC): Map JWT claims (e.g., groups) to Jakarta EE roles using @RolesAllowed. - -==== Decentralized Security - -Propagation Across Services: A JWT issued by an authentication service is propagated across microservices (e.g., the Order Service and the Inventory Service). Each service independently verifies the token and enforces access control. - -Reduced Central Dependency: No need for a central authorization server, simplifying architecture and improving -fault tolerance. - -Example: - -- Authentication Service: Issues a JWT with `sub: "user1"` and `groups: ["user"]`. -- Order Service: Validates the JWT and processes requests if groups include `users`. -- Inventory Service: Revalidates the same JWT without contacting the auth service. - -=== Setting Up MicroProfile JWT - -To use MicroProfile JWT in your project, add the following dependency to your _pom.xml_ (for Maven): - -[source, xml] ----- - - org.eclipse.microprofile.jwt - microprofile-jwt-auth-api - 2.1 - provided - ----- - -For Gradle, add the following to your _build.gradle_: - -[source] ----- -implementation 'org.eclipse.microprofile.jwt:microprofile-jwt-auth-api:2.1' ----- - -=== Configuring MicroProfile JWT Validation - -MicroProfile JWT requires validation rules configuration to be defined in `src/main/resources/microprofile-config.properties` file. Below is an example configuration: - -[source] ----- -# Public key (PEM format) to verify JWT signatures -mp.jwt.verify.publickey.location=META-INF/publicKey.pem - -# Expected issuer (e.g., your OIDC provider) -mp.jwt.verify.issuer=https://auth.example.com - -# Optional: Validate token audience -mp.jwt.verify.audiences=order-service,payment-service ----- - -Explanation: - -- The `mp.jwt.verify.publickey.location` property specifies the location of the public key used to verify the JWT’s signature. - -- The `mp.jwt.verify.issuer` property defines the expected issuer of the JWT, ensuring that tokens are only accepted if issued by a trusted authority. - -- Optionally, the `mp.jwt.verify.audiences` property can specify the allowed audiences for the JWT, ensuring that the token is intended for the service. - -=== Public Key Setup - -Place the PEM-encoded public key in _src/main/resources/META-INF/publicKey.pem_. This key is used to verify incoming JWT signatures. - -== Request Flow in MicroProfile JWT - -Understanding how JWTs are propagated and processed in a microservices architecture is critical to implementing secure and scalable authentication. This section explains the lifecycle of a JWT from client to service, including token extraction, validation, and claim usage. - -=== How JWTs are Propagated in Microservices - -JWTs are propagated via the `Authorization: Bearer` HTTP header across clients and services. - -==== Client-to-Service - -When a client authenticates (e.g., via a login endpoint), it receives a JWT from an authentication service. This token is then included in the header of subsequent requests to microservices. For example, a request header might look like this: - -[source] ----- -GET /api/orders HTTP/1.1 -Authorization: Bearer eyJhbGciOiJSUzI1NiIs… ----- - -==== Service-to-Service - -In microservices architecture, a client sends a JWT token to the initial service (for example, Order Service) using the `Authorization: Bearer` header. The initial service can forward the same token when calling another backend service (for example, the Inventory Service). Each microservice independently validates the JWT to enforce decentralized, stateless security. - -In advanced scenarios involving multiple downstream services, careful consideration must be given to validating the JWT's `aud` (audience) claim to ensure the token is intended for the target service. - -==== Token Extraction - -MicroProfile JWT runtime handles token extraction and validation automatically. The token is parsed and validated as follows: - -- Header Parsing: The runtime extracts the token from the Bearer schema. - -- Decoding: The JWT is split into its header, payload, and signature components. - -==== Token Validation -The token validation involves the following steps: - -- Signature Verification: The public key validates the token’s integrity. - -- Standard Claims Validation: The runtime then validates standard claims: - -. `iss`: It should match the `mp.jwt.verify.issuer` configuration property. - -. `exp` : This checks if the token has not expired. - -. `aud` : Optionally it checks for the included service(s) in `mp.jwt.verify.audiences`. - -If valid, the JWT’s claims populate the `SecurityContext`. Otherwise, MicroProfile JWT rejects the request with a `401 Unauthorized` status. - -=== Accessing JWT claims via `SecurityContext` - -The `SecurityContext` interface (from Jakarta EE) provides programmatic access to JWT claims. Once a token is validated, MicroProfile JWT injects the `JsonWebToken` into the `SecurityContext`, allowing developers to: - -- Retrieve user identity (e.g., `sub` claim). - -- Check user roles (e.g., `groups` claim). - -- Access custom claims (e.g., `tenant_id` claim). - -[source, java] ----- -@GET -@Path("/user-profile") -public String getUserProfile(@Context SecurityContext ctx) { - JsonWebToken jwt = (JsonWebToken) ctx.getUserPrincipal(); - String userId = jwt.getName(); // Extracts the "sub" claim - Set roles = jwt.getGroups(); // Extracts the "groups" claim - String tenant = jwt.getClaim("tenant_id"); // Custom claim - - return "User: " + userId + ", Roles: " + roles + ", Tenant: " + tenant; -} ----- - -The `SecurityContext` simplifies working with JWTs, enabling seamless integration with Jakarta EE’s security annotations like `@RolesAllowed`. By calling `securityContext.getUserPrincipal()`, the application can obtain the `JsonWebToken` instance, which contains all the claims from the JWT. - -== Role-Based Access Control (RBAC) - -MicroProfile JWT simplifies RBAC by mapping JWT claims (e.g., `groups` or `roles`) to Jakarta EE roles. This enables declarative security using the `@RolesAllowed` annotation. This section explains how to configure and use this mapping effectively. - -=== Default Role Mapping with the `groups` Claim - -MicroProfile JWT seamlessly integrates with Jakarta EE’s `@RolesAllowed` annotation to enforce role-based access control in microservices. By default, MicroProfile JWT maps roles from the groups claim in the JWT payload to Jakarta EE roles. The groups claim is a standard JWT claim that represents the roles or groups assigned to the user. For example, a JWT payload might include: - -[source] ----- -{ - "iss": "https://example.com/issuer", - "sub": "user123", - "groups": ["user", "admin"] -} ----- - -In this case, the user has two roles: user and admin. - -=== Securing Endpoints -The roles in the groups claim can be used directly with the `@RolesAllowed` annotation to secure endpoints. - -[source, java] ----- -@Path("/orders") -public class OrderResource { - - @GET - @Path("/{id}") - @RolesAllowed("user") // Only users can access this method - public Response getOrder(@PathParam("id") String id, @Context SecurityContext ctx) { - String user = ctx.getUserPrincipal().getName(); - // Fetch order for the user - return Response.ok("Order for user: " + user + ", ID: " + id).build(); - } - - @DELETE - @Path("/{id}") - @RolesAllowed("admin") // Only admins can access this method - public Response deleteOrder(@PathParam("id") String id, @Context SecurityContext ctx) { - String admin = ctx.getUserPrincipal().getName(); - // Delete order as admin - return Response.ok("Order deleted by admin: " + admin + ", ID: " + id).build(); - } -} ----- - -The `GET /orders/{id}` service is accessible to users, whereas the `DELETE /orders/{id}` is only available to users with the admin role. - -=== Custom Role Mapping - -If your JWT uses a claim other than groups to represent roles (e.g., roles or scopes), you can customize the mapping using the `mp.jwt.verify.roles` property in _microprofile-config.properties_: - -[source] ----- -# Optional: Map roles from the "groups" claim (default behavior) -mp.jwt.verify.roles=groups ----- - -The `groups` claim is the default claim used for role mapping in MicroProfile JWT Authentication. Therefore, you typically do not need to set the `mp.jwt.verify.roles` property unless your JWT uses a different claim name. For example, some identity providers (like OAuth 2.0 servers or OpenID Connect providers) might include roles in claims such as `roles`, `permissions`, or `scope` instead of `groups`. - -In such cases, update the mapping in your _microprofile-config.properties_ file: - -[source] ----- -mp.jwt.verify.roles=roles ----- - -This ensures that MicroProfile JWT Authentication correctly maps the roles for use with Jakarta EE security annotations like `@RolesAllowed`. - -==== How the RBAC Works - -- Token Validation: MicroProfile JWT validates the JWT’s signature and claims. - -- Role Extraction: Roles are extracted from the configured claim (groups by default). - -- Access Control: The `@RolesAllowed` annotation checks if the user’s roles match the required roles. If not, a `403 Forbidden` response is returned. - -This approach ensures fine-grained security while maintaining compatibility with standard JWT practices. - -== Setting Token Expiry Times for Security - -Short token expiry times reduce the surface area for the attackers. Here’s how to configure token expiry effectively: - -=== Configuring Token Expiry - -Set the `exp` claim at issuance: Ensure your authentication service issues tokens with the `exp` claim. - -[source, java] ----- -{ - "exp": 1735689600 // Token expires at 2025-01-01 00:00:00 UTC -} ----- - -MicroProfile JWT automatically validates the `exp` claim during token verification. Beyond standard JWT validation settings, no additional configuration is needed. - -MicroProfile JWT will reject tokens returning a 401 Unauthorized response if: - -- The `exp` claim is missing or invalid. - -- The current time exceeds the `exp` value. - -== Error Handling - -MicroProfile JWT automatically validates tokens and rejects invalid requests with standardized HTTP responses. Common scenarios include: - -=== Invalid Token (e.g., malformed JWT, invalid signature): - -[source] ----- -HTTP/1.1 401 Unauthorized -WWW-Authenticate: Bearer error="invalid_token" ----- - -=== Expired Token (exp claim validation failure): - -[source] ----- -HTTP/1.1 401 Unauthorized -WWW-Authenticate: Bearer error="invalid_token", error_description="Token expired" ----- - -=== Missing Token - -[source] ----- -HTTP/1.1 401 Unauthorized -WWW-Authenticate: Bearer error="missing_token" ----- - -=== Insufficient Permissions (e.g., missing role for @RolesAllowed): - -[source] ----- -HTTP/1.1 403 Forbidden ----- - -=== Best Practices for JWT Authentication - -. Use Standard Claims - Prefer the groups claim for roles unless your identity provider uses a different claim. - -. Consistent Role Names - Ensure role names (e.g., admin, user) are consistent across JWTs and @RolesAllowed annotations. - -. Least Privilege - Assign minimal required roles to endpoints to reduce security risks. - -. Combine with Other Annotations - Use @PermitAll or @DenyAll alongside @RolesAllowed for flexible security policies. - -== Security Best Practices for Microservices - -But with more services comes more complexity, and with more complexity comes a greater risk of security breaches. So, how do you secure your microservices? - -Securing microservices requires a layered approach, combining authentication, authorization, encryption, and monitoring. MicroProfile JWT simplifies access control while adhering to industry standards. Below are best practices tailored for MicroProfile JWT implementations: - -. Enforce Authentication with Validated JWTs: Ensure every request to a microservice includes a valid JWT. Configure MicroProfile JWT to validate tokens using a public key. Reject tokens with invalid signatures, missing claims, or expired exp values. - -. Implement Role-Based Access Control: Restrict endpoint access based on user roles defined in the JWT. Configure role mapping in `microprofile-config.properties` if using non-default claims - -Use Short-Lived Tokens: To minimize exposure to compromised tokens, set short expiration times (exp claim) for JWTs (e.g., 15–30 minutes). - -. Secure Token Transmission: Prevent token interception or tampering by using HTTPS to encrypt data in transit and store tokens in HTTP `Authorization: Bearer` headers (never in URLs or cookies). - -. Manage Cryptographic Keys Securely: Protect keys to sign/verify JWTs by storing public keys in secure locations (e.g., Kubernetes Secrets, AWS KMS). Rotate keys periodically and avoid hardcoding them in source control. - -. Validate and Sanitize JWT Claims: Validate all claims (e.g., iss, aud) in microprofile-config.properties, and Sanitize custom claims before use to prevent injection attacks and misuse of claims. - -. Monitor and Log Security Events: Log JWT validation errors, role mismatches, and token expiration events to detect breaches and audit access patterns. Integrate with monitoring tools (e.g., Prometheus, Grafana) to track anomalies. - -These steps will help you secure your microservices against the most common attacks. - -== Conclusion - -MicroProfile JWT offers a standards-based, interoperable approach for securing microservices. It simplifies identity propagation, access control, and stateless security across distributed services. Integrating with Jakarta EE enables secure, scalable, and interoperable authentication without a session state. - -*Further Reading:* - -* https://datatracker.ietf.org/doc/html/rfc7519[RFC 7519] -* https://github.com/eclipse/microprofile-jwt-auth[MicroProfile JWT 2.1 Spec] -* https://jakarta.ee/specifications/security/3.0/[Jakarta Security 3.0] diff --git a/chapter11/index.adoc b/chapter11/index.adoc deleted file mode 100644 index 7ca17ccc..00000000 --- a/chapter11/index.adoc +++ /dev/null @@ -1,471 +0,0 @@ -= Chapter 11: MicroProfile Rest Client -:rest-client-spec-name: MicroProfile Rest Client -:rest-client-spec-version: 3.1 - -In microservices architecture, developers often face the cumbersome task of implementing boilerplate code to consume REST APIs - manually constructing HTTP requests, parsing responses, and handling errors. The MicroProfile Rest Client specification addresses this by leveraging Jakarta RESTful Web Services (formerly JAX-RS) annotations to create type-safe Rest client interfaces. Instead of writing low-level HTTP logic, developers define Java interfaces that mirror the target service’s endpoints. At runtime, MicroProfile Rest Client dynamically generates an implementation of these interfaces, automating HTTP communication while ensuring compile-time consistency between the client and server contracts. - -This chapter introduces the MicroProfile Rest Client, a type-safe framework for simplifying service-to-service communication. We will begin by defining REST client interfaces using Jakarta RESTful Web Services annotations (`@GET`, `@Path`), configuring endpoints via MicroProfile Config, and implementing HTTP invocation. Next, we will explore handling HTTP communication, processing responses, and error handling. By the end of this chapter, you will be able to replace hand-written HTTP boilerplate code with declarative, maintainable clients while adhering to Jakarta EE and MicroProfile standards. - -== Topics to be covered: - -* Introduction to MicroProfile Rest Client -* Setting up Dependencies -* Defining a Rest Client Interface -* Parameter Configuration -* Requests and Response Handling -* Working with JSON Data formats -* Error Handling Strategies - -== Introduction to MicroProfile Rest Client - -The MicroProfile Rest Client specification simplifies RESTful service consumption in Java microservices by replacing error-prone manual HTTP handling with a type-safe, annotation-driven approach. Instead of writing boilerplate code, developers define Java interfaces that mirror the target service’s API. Using Jakarta RESTful Web Services annotations like `@GET`, and `@Path`, these interfaces declaratively map methods to HTTP operations (e.g., `/users/{id}` to `getUser(id)`). The framework then generates an implementation at runtime, automating communication while ensuring compile-time consistency between client and server contracts. Tight integration with MicroProfile Config and CDI allows seamless configuration and injection, making it ideal for building resilient, maintainable clients that align with modern microservices practices. - -==== Key Features of MicroProfile Rest Client - -The MicroProfile Rest Client simplifies consuming RESTful services in Java microservices with the following key features: - -. *Type-Safe and Declarative APIs* - The MicroProfile Rest Client allows developers to define REST clients as Java interfaces using Jakarta RESTful Web Services annotations like `@GET`, `@POST`, `@PUT`, `@DELETE`, `@Path`, `@Consumes` and `@Produces`. This approach improves code clarity and ensures compile-time validation, reducing the possibility of runtime errors . -. *Integration with CDI (Context and Dependency Injection)* - This specification allows developers to seamlessly inject MicroProfile Rest Client interfaces using `@Inject` and `@RestClient` into CDI-managed beans, promoting better dependency management and integration with other components. By leveraging CDI lifecycle management, the MicroProfile Rest Client can benefit from scope management (e.g., `@ApplicationScoped`), proxying, and automatic initialization. -. *Runtime Configurable with MicroProfile Config* - The behavior of MicroProfile Rest Client can be dynamically configured using MicroProfile Config. This allows properties like the base URL and other client settings to be adjusted without recompilation. The configuration can be provided through _microprofile-config.properties_ or environment variables, making the client highly adaptable to different environments. -. *Support for Asynchronous Execution* - For asynchronous execution, MicroProfile Rest Client can return `CompletionStage`, allowing non-blocking requests. This significantly improves performance & scalability in high-concurrency environments. -. *Automatic Handling of Redirect Responses* - MicroProfile Rest Client can automatically follow HTTP redirects, simplifying client implementation when working with services that return `3xx` responses. -. *Secure Socket Layer (SSL) and Security Configuration* - Supports SSL/TLS configuration, including certificates and trust stores, ensuring secure communication between microservices. -. *Propagation of Headers and Cookies* - Enables automatic propagation of HTTP headers, cookies and context (e.g., authentication tokens), facilitating session management across service calls. -. *Exception Handling and Custom Providers* - Allows custom exception mapping and response handling, giving developers control over error response based on specific conditions, improving fault tolerance and user experience. -. *Integration with MicroProfile Fault Tolerance* - This specification Supports resilience patterns like retries (`@Retry`), circuit breakers (`@CircuitBreaker`), and Bulkheads (`@Bulkhead`), ensuring stability in service-to-service communications. -. *Integration with MicroProfile Long Running Actions (LRA)* - MicroProfile Rest Client can coordinate distributed transactions using LRA annotations (e.g., `@LRA`), enabling compensation logic for long-running processes. This ensures consistency across services in complex workflows. -. *Portability and Standards Compliance*: This specification enables MicroProfile Rest Client to work across different MicroProfile-compatible runtimes, leveraging Jakarta EE standards (CDI, Jakarta RESTful Web Services, JSON Binding, JSON Processing). - -== Setting up Dependency for MicroProfile Rest Client - -To use MicroProfile Rest Client 3.1 in your project, you need to include the necessary dependencies in your build configuration. Below are configurations for Maven and Gradle: - -=== Maven Configuration -For Maven-based projects, add the following dependency to your pom.xml file: - -[source, xml] ----- - - org.eclipse.microprofile.rest.client - microprofile-rest-client-api - 3.1 - ----- - -=== Gradle Configuration - -For Gradle-based projects, add the following dependency to your build.gradle file: - -[source, xml] ----- -dependencies { - Implementation 'org.eclipse.microprofile.rest.client:microprofile-rest-client-api:3.1' - compileOnly 'org.eclipse.microprofile:microprofile:6.1' -} ----- - -> Tip: The MicroProfile Rest Client is an Eclipse Foundation project. For more details and updates on the project, visit the official repository: MicroProfile Rest Client on GitHub. - -== Creating MicroProfile Rest Client Interface - -To create a MicroProfile Rest Client interface, you need to define a Java interface and annotate it with annotations to map it to a RESTful service. - -=== The `@RegisterRestClient` Annotation - -To use the MicroProfile Rest Client, annotate your client interface with `@RegisterRestClient`. This annotation registers the interface as a Rest client within MicroProfile runtime and enables it as a CDI bean, allowing it to be injected into other components. - -Example: - -[source, java] ----- -package io.microprofile.tutorial.inventory.client; - -import jakarta.ws.rs.GET; -import jakarta.ws.rs.Path; -import jakarta.ws.rs.PathParam; - -import org.eclipse.microprofile.rest.client.inject.RegisterRestClient; - -import io.microprofile.tutorial.inventory.dto.Product; - -@RegisterRestClient(configKey = "product-service") -@Path("/products") -public interface ProductServiceClient { - - @GET - @Path("/{id}") - Product getProductById(@PathParam("id") Long id); -} ----- - -Explanation: -In the above code, we define a `ProductServiceClient` within the package `io.microprofile.tutorial.inventory.client`. The interface serves as a Rest client for interaction with a remote product service. - -. `@RegisterRestClient` - declares the `ProductServiceClient` interface as a MicroProfile Rest Client, enabling it to be injected into other CDI-managed components. - -. `configKey = "product-service"` - associates the client with a configuration key, allowing dynamic configuration via MicroProfile Config (e.g., using _microprofile-config.properties_ or environment variables). - -. `@Path(/products)` - specifies the base URI path segement for the RESTful service. - -. `@GET` - indicates that the `getProductById()` method handles HTTP GET requests. - -. `@Path("/{id}")` – define a dynamic URI path parameter `{id}`, which will be replaced at runtime with the actual value provided. - -. `@PathParam("id")` - binds the method parameter `id` to the `{id}` placeholder in the request URL. - -. Return Type (`Product`) - specifies that the method returns a `Product` Data Transfer Object (DTO), representing the retrieved product data. - -> Note: In CDI environments, it is recommended not to extend AutoCloseable in REST client interfaces. The container manages the lifecycle of injected clients automatically, ensuring proper resource handling without requiring manual closure. - -==== Configuration via MicroProfile Config: - -To configure the URI using MicroProfile Config, you need to add a config file named src/main/webapp/META-INF/microprofile-config.properties in your project. This file contains the configuration key and value pairs. In this example, we’re configuring the base URI to http://localhost:8080/api/products. We can configure other client properties, such as followRedirects. The followRedirects property specifies whether the client should automatically follow HTTP redirects (3xx status codes) when making RESTful web service calls. - -[source] ----- -product-service/mp-rest/url=http://localhost:8080/api/products -product-service/mp-rest/followRedirects=true ----- - -== Parameter Configurations - -In MicroProfile Rest Client, you can dynamically configure headers, query parameters, and path parameters using Jakarta RESTful Web Services annotations. These annotations bind method parameters to different parts of the HTTP request, enabling flexible and dynamic RESTful client interfaces that can efficiently interact with various endpoints. - -*Supported Parameter Annotations* - -. `@PathParam` – Binds a method parameter to a path variable in the URL. - -. `@QueryParam` – Maps a method parameter to a query string parameter in the request URL. - -. `@HeaderParam` – Attaches a method parameter to an HTTP request header. - -=== Using Path Parameters (`@PathParam`) - -Path parameters are used to insert dynamic values directly into the URL path. - -[source, java] ----- -import jakarta.ws.rs.GET; -import jakarta.ws.rs.Path; -import jakarta.ws.rs.PathParam; -import org.eclipse.microprofile.rest.client.inject.RegisterRestClient; - -@RegisterRestClient -@Path("/products") -public interface ProductServiceClient { - - @GET - @Path("/{id}") - Product getProductById(@PathParam("id") Long id); -} ----- - -Example -[source, java] ----- -productServiceClient.getProductById(1L); ----- - -Resulting HTTP Request -[source, http] ----- -GET /products/1 ----- - -==== Why Use @PathParam? - -. Ensures URL structure consistency by enforcing path variables -. Prevents hardcoding URLs, making the code cleaner and maintainable. - -=== Using Query Parameters (`@QueryParam`) - -Query parameters are typically used for filtering, pagination, or optional parameters in the request URL. - -Example: - -[source, java] ----- -import jakarta.ws.rs.GET; -import jakarta.ws.rs.Path; -import jakarta.ws.rs.QueryParam; -import org.eclipse.microprofile.rest.client.inject.RegisterRestClient; - -@RegisterRestClient -@Path("/products") -public interface ProductServiceClient { - - @GET - List getProductsByCategory(@QueryParam("category") String category); -} ----- - -Example Call: - -[source, java] ----- -productServiceClient.getProductsByCategory("electronics"); ----- - -Resulting HTTP Request: -[source, http] ----- -GET /products?category=electronics ----- - -==== Why Use @QueryParam? -. Useful for filtering results (?category=electronics). -. Ideal for pagination (?page=2&size=20). -. Allows sending optional parameters without modifying the URL structure. - -=== Using Header Parameters (@HeaderParam) - -Header parameters are typically used for authentication, authorization, and metadata transmission between client and server. - -Example: - -[source, java] ----- -import jakarta.ws.rs.GET; -import jakarta.ws.rs.HeaderParam; -import jakarta.ws.rs.Path; -import org.eclipse.microprofile.rest.client.inject.RegisterRestClient; - -@RegisterRestClient -@Path("/orders") -public interface OrderServiceClient { - - @GET - List getOrders(@HeaderParam("Authorization") String authToken); -} ----- - -Example Call: - -[source, java] ----- -orderServiceClient.getOrders("Bearer my-secret-token"); ----- - -Resulting HTTP Request: - -[source] ----- -GET /orders -Authorization: Bearer my-secret-token ----- - -==== Why Use @HeaderParam? -. Used for passing authentication tokens (Authorization: Bearer token). -. Helps with custom metadata exchange (e.g., X-Correlation-ID: 12345). -. Avoids exposing sensitive data in URLs (e.g., API keys). - -=== Overview of Additional Annotations - -. `@CookieParam` - Binds a method parameter to the value of an HTTP cookie in the incoming request. - -. `@FormParam` — Maps a method parameter to a field in a submitted HTML form (`application/x-www-form-urlencoded` POST body). - -. `@MatrixParam` — Binds a method parameter to a matrix parameter embedded within the URL path segements (e.g., `/product;color=blue;size=large`). - -. `@BeanParam` — Aggregates multiple parameter annotations (path, query, header, etc.) into a single Java bean for cleaner method signature. - -> Tip: These annotations eliminate manual string concatenation, making REST client calls type-safe and maintainable. - -== Handling Requests and Responses - -In MicroProfile Rest Client, handling requests and responses involves defining methods in your interface that map to RESTful service endpoints. This ensures that: - -. HTTP requests are automatically constructed based on method definitions. -. Responses are efficiently deserialized into Java objects (DTOs) or processed manually using `Response`. - -Using Jakarta RESTful Web Services annotations, you can define standard HTTP operations such as @GET, @POST, @PUT, and @DELETE. The framework also supports additional methods like @HEAD, @OPTIONS, and @PATCH, providing complete control over HTTP communication when needed. Meanwhile, MicroProfile automatically handles serialization, deserialization, and request execution at runtime. - -== Handling JSON Data formats - -By default, MicroProfile Rest Client supports JSON format without requiring additional configurations. Serialization and deserialization of request and response bodies are automatically handled using JSON-B (Jakarta JSON Binding) or JSON-P (Jakarta JSON Processing). - -Developers can directly use Java objects as request bodies or response entities, eliminating the need for manual parsing. - -Example: - -[source, java] ----- -import jakarta.ws.rs.GET; -import jakarta.ws.rs.Path; -import jakarta.ws.rs.Produces; -import jakarta.ws.rs.Consumes; -import org.eclipse.microprofile.rest.client.inject.RegisterRestClient; - -@RegisterRestClient -@Path("/products") -@Produces("application/json") -@Consumes("application/json") -public interface ProductServiceClient { - - @GET - @Path("/{id}") - Product getProductById(@PathParam("id") Long id); -} ----- - -*Explanation* - -. The `@Produces("application/json")` annotation specifies that the client expects JSON responses. This determines the value of the `Accept` header in HTTP requests. - -. The `@Consumes("application/json")` annotation specifies that the client sends JSON requests. This determines the value of the `Content-Type` header of the request. - -. By default the media type `"application/json"` is used if `@Produces` and `@Consumes` are not explicitly set. - -. MicroProfile Rest Client automatically serializes Java objects to JSON and deserializes responses into Product DTO (Data Transfer Object) Java object. - -=== Error Handling - -Effective error handling is crucial when consuming remote RESTful services. MicroProfile Rest Client provides a structured approach to error handling by mapping HTTP responses to exceptions using the `ResponseExceptionMapper` interface. - -This mechanism allows developers to: - -. Convert specific HTTP response codes into custom exceptions. -. Customize exception handling behavior at runtime. -. Automatically throw mapped exceptions in client invocations. - -==== Using `ResponseExceptionMapper` interface - -The `ResponseExceptionMapper` interface allows mapping an HTTP Response object to a `Throwable` (custom exception). This improves error handling by ensuring meaningful exceptions are thrown instead of manually checking response codes. - -*How it Works* - -. *Scanning and Prioritizing Exception Mappers*: When a client method is invoked, the runtime scans all registered `ResponseExceptionMapper` implementations. Mappers are then sorted in ascending order of priority, determined by the `@Priority` annotation. The mapper with the lowest numeric priority value is checked first. - -. *Handling Responses*: The `handles(int status, MultivaluedMap headers)` method determines whether a mapper should handle a given response. By default, it handles responses with status code 400 or higher, but we can override this behavior. - -. *Converting the Response to an Exception*: The `toThrowable(Response response)` method converts a response into a `Throwable` (exception). Checked exceptions are only thrown if the client method declares that it throws that type of exception of its superclass. Unchecked exceptions (`RuntimeException`) are always thrown. - -Example: - -[source, java] ----- -package io.microprofile.tutorial.inventory.client; - -import jakarta.ws.rs.GET; -import jakarta.ws.rs.Path; -import jakarta.ws.rs.PathParam; -import jakarta.ws.rs.core.Response; -import org.eclipse.microprofile.rest.client.inject.RegisterRestClient; -import org.eclipse.microprofile.rest.client.annotation.RegisterProvider; - -@RegisterRestClient(configKey = "product-service") -@RegisterProvider(ProductServiceResponseExceptionMapper.class) -@Path("/products") -public interface ProductServiceClient extends AutoCloseable { - - @GET - @Path("/{id}") - Response getProductById(@PathParam("id") Long id); -} ----- - -Explanation: - -. The REST client interface defines an endpoint for retrieving products. -. The `@RegisterProvider` annotation registers `ProductServiceResponseExceptionMapper`, ensuring custom exception handling. - -And below is the corresponding `ResponseExceptionMapper`: - -[source, java] ----- -package io.microprofile.tutorial.inventory.client; - -import jakarta.ws.rs.core.Response; -import org.eclipse.microprofile.rest.client.ext.ResponseExceptionMapper; -import io.microprofile.tutorial.inventory.dto.ProductNotFoundException; - -public class ProductServiceResponseExceptionMapper implements ResponseExceptionMapper { - - @Override - public Throwable toThrowable(Response response) { - if (response.getStatus() == 404) { - return new ProductNotFoundException("Product not found"); - } - return new Exception("An unexpected error occurred"); - } -} ----- -*Explanation:* - -If the response status code is `404`, a `ProductNotFoundException` is thrown. Otherwise, a generic exception is returned. - -=== Using the `RestClientBuilder` Class - -While *CDI-based injection* is commonly used for REST clients in MicroProfile, programmatic creation using the `RestClientBuilder` class is beneficial when CDI is unavailable or when dynamic client instantiation is required. This builder provides a *fluent API* for configuring and constructing REST client proxies without relying on constructors that require numerous arguments. - -Using `RestClientBuilder` simplifies object creation, improves code readability, and supports *method chaining*, where each configuration method returns the builder instance itself. - -==== Example: Inventory Service Calls Product Service - -In the MicroProfile Ecommerce Store, the `InventoryService` must verify whether a product exists before checking or updating inventory. This interaction can be handled by calling the `ProductService` using a REST client interface. - -[source,java] ----- -package io.microprofile.tutorial.store.inventory.service; - -import io.microprofile.tutorial.store.inventory.client.ProductServiceClient; -import io.microprofile.tutorial.store.product.entity.Product; -import org.eclipse.microprofile.rest.client.RestClientBuilder; - -import java.net.URI; -import java.util.concurrent.TimeUnit; - -public class InventoryService { - - public boolean isProductAvailable(Long productId) { - URI productApiUri = URI.create("http://localhost:8080/api"); - - try (ProductServiceClient productClient = RestClientBuilder.newBuilder() - .baseUri(productApiUri) - .connectTimeout(3, TimeUnit.SECONDS) - .readTimeout(5, TimeUnit.SECONDS) - .build(ProductServiceClient.class)) { - - Product product = productClient.getProductById(productId); - return product != null; - - } catch (Exception e) { - // Log exception (omitted for brevity) - return false; - } - } -} ----- - -==== Explanation - -- The `isProductAvailable()` method accepts a product ID and returns `true` if the product exists in the catalog. -- A `URI` object is created pointing to the base path of the ProductService API using `URI.create()`. -- A `ProductServiceClient` instance is created using the builder pattern inside a `try-with-resource` block: - * `newBuilder()` initializes the client builder. - * `baseUri()` sets the root endpoint of the target service. - * `connectTimeout()` and `readTimeout()` define connection and read timeouts respectively. - * `build()` finalizes and returns the configured client proxy. -- Because `ProductServiceClient` extends `AutoCloseable`, the try-with-resources block ensures that the client is automatically closed after the operation, preventing resource leaks. -- If a `Product` object is successfully returned, `true` is returned. -- Any exceptions are caught and handled appropriately, returning `false` in case of failure. - -This approach is especially useful for *utility services*, *batch jobs*, or environments where REST client configuration must be *dynamic or conditional*, and manual client lifecycle management is necessary. - -> Tip: When building MicroProfile REST clients programmatically (using `RestClientBuilder`), ensure that your client interface extends `AutoCloseable` and uses try-with-resources to release resources automatically. - -=== Conclusion - -The MicroProfile Rest Client provides a declarative, type-safe, and efficient mechanism for interacting with RESTful services in Java microservices. It reduces boilerplate code and lets developers focus on core business logic while still offering fine-grained control through features like `RestClientBuilder`. - -By integrating seamlessly with other MicroProfile specifications—such as *Config*, *Fault Tolerance*, and *JWT Authentication*—the Rest Client helps enhance the *security*, *resilience*, and *maintainability* of cloud-native applications. - -==== Key Takeaways - -- Removes boilerplate HTTP code, improving clarity and maintainability. -- Automatically handles JSON serialization and deserialization. -- Supports *CDI injection* for managed client lifecycles. -- Integrates with *Fault Tolerance* for retries, timeouts, and circuit breakers. -- Enhances *security* through header propagation and authentication mechanisms. - -With MicroProfile Rest Client, building robust and maintainable microservices that communicate over REST becomes *simpler*, *more flexible*, and *more powerful*. This concludes the MicroProfile tutorial. You are now equipped with the foundational knowledge to build robust, cloud-native microservices using the MicroProfile specification. Thank you for following along, and happy coding! diff --git a/fix-edit-links.sh b/fix-edit-links.sh index 0044a2a3..734d22ca 100755 --- a/fix-edit-links.sh +++ b/fix-edit-links.sh @@ -14,7 +14,7 @@ SITE_DIR="/workspaces/microprofile-tutorial/build/site" # GitHub repository information REPO_URL="https://github.com/microprofile/microprofile-tutorial" -BRANCH="patch-15" +BRANCH="main" PATH_PREFIX="modules/ROOT/pages/" # Check if the site directory exists diff --git a/modules/ROOT/pages/chapter01/chapter01.adoc b/modules/ROOT/pages/chapter01/chapter01.adoc index 89de68af..9fcd2746 100644 --- a/modules/ROOT/pages/chapter01/chapter01.adoc +++ b/modules/ROOT/pages/chapter01/chapter01.adoc @@ -76,7 +76,7 @@ MicroProfile specifications are divided into two main categories: Platform and S :figure-caption: Figure .MicroProfile Specifications -image::http://microprofile.io/wp-content/uploads/2023/10/microprofile_release_6.1.png[MicroProfile 6.1] +image::https://microprofile.io/wp-content/uploads/2023/10/microprofile_release_6.1.png[MicroProfile 6.1] === MicroProfile Platform Component Specifications