Factory Design Pattern using Spring Boot
In this guide, we will be implementing Factory Design Pattern using the spring-boot.
What is a Design Pattern?
Design patterns are specific solutions to common problems in software design. There are multiple kinds of design patterns available and here we are going to discuss one of the Creational Design Patterns which is the Factory Design Pattern.
What is a Factory Design Pattern?
Factory Design Pattern is a creational design pattern that provides an interface for creating objects in a superclass but allows subclasses to alter the type of objects that will be created. You can find more details here.
The Usecase
In the guide we will take the exapmle of Payment Provider(PayU and Paypal) and follwoing are the objectives we would like to achieve-
- Create
PaymentProviderFactory
to create payment different type of payment providers based on client need. - Accept Payment(a dummy implementation) using the payment provider created in the 1st step.
- Decouple
PaymentProvider
creation logic from normal business logic.
How to implement Factory Design Pattern in Spring Boot?
In this guide, we will go step by step and implement Factory Design Pattern using spring boot.
Step 1: Create the project
The 1st step is to initialize your project using Spring Boot Initializer. We only need Lombok as a dependency to simplify our logging. You can use this link to initialize your project with the config user in this guide. Click on Generate, it will download the initial project. Import it into your favorite IDE. I’ll be using IntelliJ IDEA you can use your choice of IDE.
Step 2: Configuring spring boot application run some code on application startup
There are multiple ways to do this but here we will be using the CommandLineRunner
approach.
Open FactoryDesignPatternApplication
class and extend it by CommandLineRunner
interface and override run()
. Your class will look like below
@Slf4j
@SpringBootApplication
public class FactoryDesignPatternApplication implements CommandLineRunner {
public static void main(String[] args) {
SpringApplication.run(FactoryDesignPatternApplication.class, args);
}
@Override
public void run(String... args) throws Exception {
log.info("Hello spring boot");
}
}
Try running your application to verify if you get Hello spring boot
in the last line-
. ____ _ __ _ _
/\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
\\/ ___)| |_)| | | | | || (_| | ) ) ) )
' |____| .__|_| |_|_| |_\__, | / / / /
=========|_|==============|___/=/_/_/_/
:: Spring Boot :: (v3.0.4)
2023-03-20T15:56:43.056+05:30 INFO 99547 --- [ main] c.v.p.f.FactoryDesignPatternApplication : Starting FactoryDesignPatternApplication using Java 17.0.5 with PID 99547 (/Users/vipulkumar/code/factory-design-pattern/target/classes started by vipulkumar in /Users/vipulkumar/code/factory-design-pattern)
2023-03-20T15:56:43.058+05:30 INFO 99547 --- [ main] c.v.p.f.FactoryDesignPatternApplication : No active profile set, falling back to 1 default profile: "default"
2023-03-20T15:56:43.306+05:30 INFO 99547 --- [ main] c.v.p.f.FactoryDesignPatternApplication : Started FactoryDesignPatternApplication in 0.524 seconds (process running for 0.763)
2023-03-20T15:56:43.307+05:30 INFO 99547 --- [ main] c.v.p.f.FactoryDesignPatternApplication : Hello spring boot
Process finished with exit code 0
Step 3: Implementing Payment Providers
Lets start with PaymentProvider
which will be an interface with a single acceptPayment()
method which will act as a super class for all kind of payment providers.
package com.vksviit.patterns.factorydesignpattern;
public interface PaymentProvider {
public void acceptPayment();
}
Now, its time to create the implementation calsses for PaymentProvider
imterface. Here we will create three implementations as follows — Stripe, PayPal and PayU.
package com.vksviit.patterns.factorydesignpattern;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
@Service(StripePaymentProvider.BEAN_ID)
@Slf4j
public class StripePaymentProvider implements PaymentProvider {
public static final String BEAN_ID = "stripePaymentProvider";
@Override
public void acceptPayment() {
log.info("Accepting payment using Stripe");
}
}
In the above sample we have done multiple things —
- Extending the class using
PaymentProvider
interface. - Annotated the class with
@Service
and given it a a uniqueBEAN_ID
. - Annotated the class with
@Slf4j
annotation to uselog.info()
line. - Finnaly overriden
acceptPayment()
method with a simple logging line.
Following the same above approach, Lets create 2 more implementation of PaymentProvider
interface.
package com.vksviit.patterns.factorydesignpattern;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
@Service(PayPalPaymentProvider.BEAN_ID)
@Slf4j
public class PayPalPaymentProvider implements PaymentProvider {
public static final String BEAN_ID = "payPalPaymentProvider";
@Override
public void acceptPayment() {
log.info("Accepting payment using PayPal");
}
}
Nothing much changed here apart from class name, BEAN_ID value and logging line.
Now the last implementation for PayU. And here it is —
package com.vksviit.patterns.factorydesignpattern;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
@Service(PayUPaymentProvider.BEAN_ID)
@Slf4j
public class PayUPaymentProvider implements PaymentProvider {
public static final String BEAN_ID = "payUPaymentProvider";
@Override
public void acceptPayment() {
log.info("Accepting payment using PayU");
}
}
Step 4: Implementing a factory using Spring Boot
With the beauty of spring framework creating a factory for the above implementation is very simple you just have write following lines —
package com.vksviit.patterns.factorydesignpattern;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import java.util.Map;
@Service
@RequiredArgsConstructor
public class PaymentProviderFactory {
private final Map<String, PaymentProvider> paymentProviderMap;
public PaymentProvider getPaymentProvider(String paymentProviderType) {
return paymentProviderMap.get(paymentProviderType);
}
}
And spring will magicily inject
all the instances of PaymentProvider
into paymentProviderMap
with BEAN_ID
of respective @Service
as key and actual instance as value. Now, we can use getPaymentProvider()
by passing paymentProviderType
as BEAN_ID
of respective implementations and use the returned instance to call methods present in theie respective implementations.
Note: With @RequiredArgsConstructor
we are using constructor injection for paymentProviderMap
.
Step 5: Using Factory to get specific implementations
This the last step. Lets goto our FactoryDesignPatternApplication
class and modifiy it to use PaymentProviderFactory
to accpet payment —
package com.vksviit.patterns.factorydesignpattern;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@Slf4j
@SpringBootApplication
public class FactoryDesignPatternApplication implements CommandLineRunner {
@Autowired
PaymentProviderFactory paymentProviderFactory;
public static void main(String[] args) {
SpringApplication.run(FactoryDesignPatternApplication.class, args);
}
@Override
public void run(String... args) throws Exception {
log.info("Hello spring boot");
// Provides StripePaymentProvider instance usage the same to accept payment
paymentProviderFactory.getPaymentProvider(StripePaymentProvider.BEAN_ID).acceptPayment();
// Provides PayPalPaymentProvider instance usage the same to accept payment
paymentProviderFactory.getPaymentProvider(PayPalPaymentProvider.BEAN_ID).acceptPayment();
// Provides PayUPaymentProvider instance usage the same to accept payment
paymentProviderFactory.getPaymentProvider(PayUPaymentProvider.BEAN_ID).acceptPayment();
}
}
Few points to consider here —
- We have injected
PaymentProviderFactory
using@Autowired
. - Using
StripePaymentProvider.BEAN_ID
,PayPalPaymentProvider.BEAN_ID
andPayUPaymentProvider.BEAN_ID
to refer the specific implementation ofPaymentProvider
implementation.
Step 6: Run the application to see the results in action
Run the application and you should be able to see the below output —
. ____ _ __ _ _
/\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
\\/ ___)| |_)| | | | | || (_| | ) ) ) )
' |____| .__|_| |_|_| |_\__, | / / / /
=========|_|==============|___/=/_/_/_/
:: Spring Boot :: (v3.0.4)
2023-03-20T16:56:55.911+05:30 INFO 4380 --- [ main] c.v.p.f.FactoryDesignPatternApplication : Starting FactoryDesignPatternApplication using Java 17.0.5 with PID 4380 (/Users/vipulkumar/code/factory-design-pattern/target/classes started by vipulkumar in /Users/vipulkumar/code/factory-design-pattern)
2023-03-20T16:56:55.913+05:30 INFO 4380 --- [ main] c.v.p.f.FactoryDesignPatternApplication : No active profile set, falling back to 1 default profile: "default"
2023-03-20T16:56:56.166+05:30 INFO 4380 --- [ main] c.v.p.f.FactoryDesignPatternApplication : Started FactoryDesignPatternApplication in 0.442 seconds (process running for 0.626)
2023-03-20T16:56:56.167+05:30 INFO 4380 --- [ main] c.v.p.f.FactoryDesignPatternApplication : Hello spring boot
2023-03-20T16:56:56.167+05:30 INFO 4380 --- [ main] c.v.p.f.StripePaymentProvider : Accepting payment using Stripe
2023-03-20T16:56:56.167+05:30 INFO 4380 --- [ main] c.v.p.f.PayPalPaymentProvider : Accepting payment using PayPal
2023-03-20T16:56:56.167+05:30 INFO 4380 --- [ main] c.v.p.f.PayUPaymentProvider : Accepting payment using PayU
Process finished with exit code 0
Last 3 lines in these logs are very important which is getting printed from the log.info()
lines which we printed in Specific implementations of PaymentProvider
.
That takes to the end of this guide. Hope you loved it and will be using it in your code. You can find the source code of this guide on my github account.