In enterprise applications, it is common to have different implementations to our business requirements. At run time we need to execute one of those based on conditions. Common examples are sorting algorithms, data encryption, notification services, authentication services, payment services and much more.
Instead of using if-else conditions, we can use a strategy design pattern to implement it. Strategy design pattern will help us to run/use different implementations at run time. Strategy approach provides code reusability, flexibility, separation of concerns and extensibility.
Consider you have developed a unified authentication service which will receive requests from different channels/clients. At run time, based on configuration, you need to decide the implementation used for authentication purposes (This could be request forward too). Your implementations can be okta, Azure Ad, or Your Own IAM.
Note: We need to implement providers as independent modules and add it to the main project as dependency.
Strategy Interface
public interface Authentication {
AuthenticationRes authentication(AuthenticationReq authenticationReq);
OtpDevicesRes otpDevices(OtpDevicesReq otpDevicesReq);
SendOtpRes sendOtp(SendOtpReq sendOtpReq);
VerifyOtpRes verifyOtp(VerifyOtpReq verifyOtpReq);
}
Concrete Strategies
@Component("oktaAuthentication")
@Slf4j
public class OktaAuthentication implements Authentication {
--------------
--------------
--------------
}
@Component("ferAuthentication")
@Slf4j
public class FerAuthentication implements Authentication {
--------------
--------------
--------------
}
@Component("eapAuthentication")
@Slf4j
public class EapAuthentication implements Authentication {
--------------
--------------
--------------
}
Service
@Service
@Slf4j
public class AuthenticationService {
public Map<String, Authentication> authenticationProvidersMap;
public Set<String> availableAuthProviders;
public AuthenticationService(Map<String, Authentication> authenticationProvidersMap) {
this.authenticationProvidersMap = authenticationProvidersMap;
this.availableAuthProviders = this.authenticationProvidersMap.keySet();
log.info("Available Auth providers:{}", this.availableAuthProviders);
}
public AuthenticationRes getAuthentication(AuthenticationReq authenticationReq, ClientDetails clientDetails) {
//This method will identify authentication provider based on client details
// and returns oktaAuthentication/eapAuthentication/ferAuthentication
String authProvider = getAuthProviderDetails(clientDetails);
if (this.availableAuthProviders.contains(authProvider)) {
return this.authenticationProvidersMap.get(authProvider)
.authentication(authenticationReq);
} else {
throw new AuthProviderUnavailable(authProvider);
}
}
public String getAuthProviderDetails(ClientDetails clientDetails) {
// implement your business logic to return the provider that need to be used.
}
}
If you have any question please leave a question in comments section.