Implementing Strategy Pattern with Spring Plugin: A Payment Processing Example
Jether Rodrigues
Senior Software Engineer | Java | Kotlin | Spring | Web3 | Solidity | Smart Contracts | Blockchain | Chainlink Advocate
Introduction
In the realm of microservices, handling varying business logic elegantly is crucial for maintaining a clean and extensible codebase. The Strategy Design Pattern is a behavioral design pattern that facilitates the selection of an algorithm’s implementation at runtime from a family of similar algorithms. In a Spring Boot environment, this pattern becomes even more powerful with the integration of Spring Plugin, a library that simplifies implementing the Strategy Pattern. This article delves into an example of processing payments using different payment strategies, illustrating how Spring Plugin eases the implementation of the Strategy Pattern in a Spring Boot application.
The Strategy?Pattern
The essence of the Strategy Pattern lies in creating a family of interchangeable algorithms that can be selected and executed at runtime. This is achieved by defining a common interface for a set of classes representing different algorithms or strategies. The client code can then choose the appropriate implementation based on the context, without being tightly coupled to a particular algorithm.
Implementing Payment Strategies with Spring?Plugin
We’ll explore a practical example based on a payment processing scenario, where different payment strategies like Credit Card and Debit are required. The example is retrieved from a GitHub repository which demonstrates the implementation of the Strategy Pattern using Spring Plugin in a Spring Boot application.
Dependencies
<dependency>
<groupId>org.springframework.plugin</groupId>
<artifactId>spring-plugin-core</artifactId>
<version>3.0.0</version>
</dependency>
<dependency>
<groupId>org.springframework.plugin</groupId>
<artifactId>spring-plugin-metadata</artifactId>
<version>3.0.0</version>
</dependency>
Strategy Interface and implementations from?it
First, we define a common interface PaymentStrategy with a method performPayment which all payment strategy implementations will adhere to. Next, we create separate classes for each payment strategy implementing the PaymentStrategy.
interface PaymentStrategy : Plugin<TicketPayment> {
fun performPayment(value: TicketPayment)
}
class CreditPaymentStrategy : PaymentStrategy {
override fun performPayment(value: TicketPayment) {
println("Executing credit payment for [ $value ]!")
}
override fun supports(ticketPayment: TicketPayment): Boolean =
ticketPayment.type == PaymentType.CREDIT
}
class DebitPaymentStrategy : PaymentStrategy {
override fun performPayment(value: TicketPayment) {
println("Executing debit payment for [ $value ]!")
}
override fun supports(ticketPayment: TicketPayment): Boolean =
ticketPayment.type == PaymentType.DEBIT
}
Injecting Strategies with Spring?Plugin
With Spring Plugin, we can easily inject all implementations of PaymentStrategy through Plugin Registry.
领英推荐
class PaymentService(private val pluginRegistry: PluginRegistry<PaymentStrategy, TicketPayment>) {
fun processPayment(payment: TicketPayment) {
pluginRegistry.getPluginFor(payment).ifPresent { strategy ->
strategy.performPayment(payment)
}
}
}
In this setup, Spring Plugin handles the injection of all PaymentStrategy implementations into the PaymentService class. The processPayment method finds the correct strategy based on the payment type, and delegates the payment processing to the selected strategy.
Strategies and Service Configurations
@Configuration
class PaymentStrategyConfig {
@Bean
@Qualifier("creditPaymentStrategy")
fun creditPaymentStrategy() = CreditPaymentStrategy()
@Bean
@Qualifier("debitPaymentStrategy")
fun debitPaymentStrategy() = DebitPaymentStrategy()
}
@Configuration
class PaymentServiceConfig {
@Bean
fun paymentService(pluginRegistry: PluginRegistry<PaymentStrategy, TicketPayment>) =
PaymentService(pluginRegistry)
}
Enabling Spring Plugin?Registry
@SpringBootApplication
@EnablePluginRegistries(
value = [
PaymentStrategy::class
]
)
class DemoStrategyApplication
fun main(args: Array<String>) {
runApplication<DemoStrategyApplication>(*args)
}
Injecting PaymentService into the Controller
@Validated
@RestController
@RequestMapping("/v1/api/tickets")
internal class DemoStrategyController(private val paymentService: PaymentService) {
@PostMapping("/pay")
fun pay(@RequestBody ticketPaymentRequest: TicketPaymentRequest): ResponseEntity<TicketPaymentResponse> =
ticketPaymentRequest.to().let { payment ->
paymentService.processPayment(payment).let {
ResponseEntity
.accepted()
.body(TicketPaymentResponse.from(payment))
}
}
}
For executing some tests, just execute the command below using the options CREDIT and DEBIT for the paymentType property.
curl --location 'https://localhost:9090/v1/api/tickets/pay' \
--header 'Accept: application/json' \
--header 'Content-Type: application/json'
--data '{
"amount": 100.00,
"paymentType": "DEBIT"
}'
The source code presented in this article: https://github.com/jether2011/demo-spring-plugin-strategy
Conclusion
The combination of the Strategy Design Pattern with Spring Plugin provides a clean and efficient way to handle varying business logic in a Spring Boot application. Through a practical payment processing example, we have seen how Spring Plugin simplifies the management and injection of different strategy implementations, promoting a clean and modular code structure.