You are currently viewing Implementing a Standard Base Line Service with Docker Sidecar Pattern

Implementing a Standard Base Line Service with Docker Sidecar Pattern

Introduction

In the fast-paced modern environment for software development, handling multiple services performing similar functionalities across different projects can be daunting. Decreasing the redundancy of services and having the same performance and reliability is desired. We have developed an SBLS—a ‘Standard Base Line Service’—for currency conversions by using Docker, Spring Boot, and the circuit breaker pattern. This blog post will take a developer through creating a standard service image that can be reused throughout projects and allowing scalability enhancements to be added via a sidecar.

Use Case

This company faces a common problem: many internal projects have their implementations of services that perform almost identical tasks. This redundancy leads to increased maintenance efforts and consistency issues. To streamline our services, we aim to create a standard base service for currency conversion. This service will:

  • Perform basic currency conversion.
  • Call a sidecar service for premium conversion rates if a specific flag is set.

Exploring Possible Solutions

  1. Monolithic Approach: Integrate all functionalities into a single service. This straightforward approach leads to a large, unwieldy codebase that requires more effort to maintain and scale.

  2. Microservices without Docker: Separate the functionalities into distinct microservices. While this offers better modularity, managing dependencies and deployment can become complex without containerization.

  3. Microservices with Docker: Use Docker to containerize services, providing isolated environments and simplifying deployment. This approach is flexible, scalable, and manageable.

Why REST with Docker?

After evaluating the options, we decided on a REST-based microservices architecture using Docker. Docker provides:

  • Isolation: Each service runs in its container, ensuring consistency across environments.
  • Scalability: Services can be scaled independently based on demand.
  • Ease of Deployment: Docker simplifies deployment, reducing errors and streamlining operations.

Full Solution

Let’s dive into the solution, which involves creating a base currency conversion service and a sidecar service for premium conversion rates. We’ll use Java 17, Spring Boot 3.2, and Docker, integrating Resilience4j for circuit breaker functionality to enhance resilience.

Understanding the Sidecar Pattern and Its Implementation

What is the Sidecar Pattern?

The sidecar pattern is a design pattern used in microservices architecture where a secondary service (the sidecar) runs alongside the primary application service. This secondary service can provide various auxiliary functionalities such as logging, monitoring, or, as in our case, extended business logic.

The primary service (SBLS) and the sidecar service share the same lifecycle, meaning they are deployed and scaled together. The sidecar pattern helps keep the primary service simple and focused on its core functionality while the sidecar handles additional features.

Why Use the Sidecar Pattern?

  1. Separation of Concerns: The sidecar pattern separates the primary business logic from auxiliary or extended functionalities, making the primary service easier to maintain and understand.

  2. Modularity: It allows for modular development. Different teams can work independently on the primary and sidecar services, enhancing productivity and reducing complexity.

  3. Flexibility: The sidecar can be easily updated, replaced, or scaled without affecting the primary service, allowing for greater flexibility and resilience.

  4. Enhanced Capabilities: In our use case, the sidecar provides a premium currency conversion rate, extending the capabilities of the SBLS without altering its core functionality.

Using an Environment Variable for the Sidecar Service

We use an environment variable (USE_SIDECAR) to control whether the sidecar service should be utilized. This approach provides several benefits:

  1. Dynamic Configuration: An environment variable allows you to dynamically configure the service behavior without changing the code. This is particularly useful in deployment environments (development, testing, production).

  2. Feature Toggle: The environment variable acts as a feature toggle, allowing you to easily enable or disable the sidecar service. This is helpful for A/B testing, gradual rollouts, or the sidecar service is temporarily unavailable.

  3. Simplicity: It simplifies the deployment process. You can use the same Docker image and change the environment variable to alter the service behavior, avoiding the need to build and maintain multiple image versions.

Implementation in Our Solution

In our currencyconversion service, we check the USE_SIDECAR environment variable to decide whether to call the sidecar service for premium currency conversion rates. Here’s a snippet from our CurrencyConversionService:

@Service
public class CurrencyConversionService {

    ...

    @Value("${premium.rate.url}")
    private String premiumRateUrl;

    @Value("${use.sidecar:false}")
    private boolean useSidecar;

    ...

By incorporating this pattern, we achieve a flexible, modular, and resilient architecture that simplifies maintenance and enhances functionality without complicating the core service.

This next section should help in understanding the rationale behind using the sidecar pattern and the environment variable for flagging its usage in our solution.

Step 1: Create the Currency Conversion Service (SBLS) and Sidecar Service Directory Structure

Directory Structure

projects/
├── currency-conversion-service/
│   ├── src/
│   │   ├── main/
│   │   │   ├── java/
│   │   │   │   └── com/
│   │   │   │       └── example/
│   │   │   │           └── currencyconversion/
│   │   │   │               ├── CurrencyConversionServiceApplication.java
│   │   │   │               ├── controller/
│   │   │   │               │   └── CurrencyConversionController.java
│   │   │   │               └── service/
│   │   │   │                   └── CurrencyConversionService.java
│   │   │   └── resources/
│   │   │       └── application.properties
│   ├── Dockerfile
│   └── pom.xml
└── premium-rate-service/
    ├── src/
    │   ├── main/
    │   │   ├── java/
    │   │   │   └── com/
    │   │   │       └── example/
    │   │   │           └── premiumrate/
    │   │   │               ├── PremiumRateServiceApplication.java
    │   │   │               ├── controller/
    │   │   │               │   └── PremiumRateController.java
    │   │   └── resources/
    │   │       └── application.properties
    ├── Dockerfile
    └── pom.xml
docker-compose.yml

Step 2: Create the Currency Conversion (SBLS) and Sidecar Services

CurrencyConversionServiceApplication.java

package com.example.currencyconversion;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class CurrencyConversionServiceApplication {
    public static void main(String[] args) {
        SpringApplication.run(CurrencyConversionServiceApplication.class, args);
    }
}

CurrencyConversionController.java

package com.example.currencyconversion.controller;

import com.example.currencyconversion.service.CurrencyConversionService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

@RestController
@RequestMapping("/convert")
public class CurrencyConversionController {

    @Autowired
    private CurrencyConversionService conversionService;

    @GetMapping
    public double convertCurrency(@RequestParam double amount, @RequestParam String from, @RequestParam String to, @RequestParam boolean usePremiumRate) {
        return conversionService.convertCurrency(amount, from, to, usePremiumRate);
    }
}

CurrencyConversionService.java

package com.example.currencyconversion.service;

import io.github.resilience4j.circuitbreaker.annotation.CircuitBreaker;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;

@Service
public class CurrencyConversionService {

    @Value("${premium.rate.url}")
    private String premiumRateUrl;

    @Value("${use.sidecar:false}")
    private boolean useSidecar;

    private final RestTemplate restTemplate = new RestTemplate();

    public double convertCurrency(double amount, String from, String to, boolean usePremiumRate) {
        double basicRate = getBasicRate(from, to);
        if (usePremiumRate && useSidecar) {
            double premiumRate = getPremiumRate(from, to);
            return amount * premiumRate;
        }
        return amount * basicRate;
    }

    private double getBasicRate(String from, String to) {
        // Basic conversion rate logic
        return 1.1; // Placeholder
    }

    @CircuitBreaker(name = "premiumRateCircuitBreaker", fallbackMethod = "fallbackPremiumRate")
    private double getPremiumRate(String from, String to) {
        String url = premiumRateUrl + "?from=" + from + "&to=" + to;
        return restTemplate.getForObject(url, Double.class);
    }

    public double fallbackPremiumRate(String from, String to, Throwable throwable) {
        // Fallback logic if premium rate service fails
        return 1.15; // Placeholder for fallback rate
    }
}

application.properties

# Services Properties
premium.rate.url=http://premium-rate-service:8081/rate
use.sidecar=${USE_SIDECAR:false}

# Circuit Breaker Properties
resilience4j.circuitbreaker.instances.premiumRateCircuitBreaker.slidingWindowSize=10
resilience4j.circuitbreaker.instances.premiumRateCircuitBreaker.permittedNumberOfCallsInHalfOpenState=3
resilience4j.circuitbreaker.instances.premiumRateCircuitBreaker.minimumNumberOfCalls=5
resilience4j.circuitbreaker.instances.premiumRateCircuitBreaker.failureRateThreshold=50
resilience4j.circuitbreaker.instances.premiumRateCircuitBreaker.waitDurationInOpenState=10000

Dockerfile

FROM openjdk:17-jdk-slim
VOLUME /tmp
COPY target/currency-conversion-service.jar app.jar
ENTRYPOINT ["java","-jar","/app.jar"]

PremiumRateServiceApplication.java

package com.example.premiumrate;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class PremiumRateServiceApplication {
    public static void main(String[] args) {
        SpringApplication.run(PremiumRateServiceApplication.class, args);
    }
}

PremiumRateController.java

package com.example.premiumrate.controller;

import org.springframework.web.bind.annotation.*;

@RestController
@RequestMapping("/rate")
public class PremiumRateController {

    @GetMapping
    public double getPremiumRate(@RequestParam String from, @RequestParam String to) {
        // Premium conversion rate logic
        return 1.2; // Placeholder
    }
}

application.properties

# Add any required properties here

Dockerfile

FROM openjdk:17-jdk-slim
VOLUME /tmp
COPY target/premium-rate-service.jar app.jar
ENTRYPOINT ["java","-jar","/app.jar"]

Step 3: Create Combined Docker Compose File

Create a combined docker-compose.yml file to deploy both services.

docker-compose.yml

version: '3.8'
services:
  currency-conversion-service:
    build:
      context: ./currencyconversion
      dockerfile: Dockerfile
    image: currencyconversion
    environment:
      - PREMIUM_RATE_URL=http://premium-rate-service:8081/rate
      - USE_SIDECAR=true
    networks:
      mynetwork:
        ipv4_address: 172.25.0.2
    ports:
      - "12080:8080"
    depends_on:
      - premium-rate-service

  premium-rate-service:
    build:
      context: ./premiumrate
      dockerfile: Dockerfile
    image: premiumrate
    networks:
      mynetwork:
        ipv4_address: 172.25.0.3
    ports:
      - "8081:8081"

networks:
  mynetwork:
    driver: bridge
    ipam:
      config:
        - subnet: 172.25.0.0/16

Step 4: Build and Deploy Services

  1. Build the currencyconversion service
cd projects/currencyconversion
mvn clean package
docker build -t currencyconversion .
  1. Build the premiumrate service
cd projects/premiumrate
mvn clean package
docker build -t premiumrate .
  1. Run Docker Compose for both services
cd projects
docker-compose up

Step 5: Test the Services

Use Postman to test the currency-conversion-service:

  • Endpointhttp://192.168.0.88:12080/convert?amount=100&from=USD&to=EUR&usePremiumRate=true
  • Response{ "result": 120.0 } if the sidecar is used.
  • Response{ "result": 110.0 } if the sidecar is not used.

Conclusion

Following these steps, you can create a resilient, scalable, and maintainable currency conversion service using Docker, Spring Boot, and Resilience4j. This setup demonstrates using the sidecar pattern to extend functionalities without modifying the base service, ensuring consistency and flexibility across your projects.

Feel free to reach out for any further assistance or questions regarding this implementation. Happy coding!