if statements tend to duplicate throughout the code base. This may lead to subtle mistakes and bugs. One way to avoid that problem is to eliminate if statement completely. Today we are going to take a look at one example of such elimination. Code examples today will be in Kotlin.
Problem at hand
Our API has an endpoint for issuing some sort of verification token given device id and phone number of the user’s mobile device.
We need to integrate these verification tokens with 3rd party API.
The format of verification token is fairly standardized.
After initial research, it turned out that issuer field of verification token has to be URL of the API that has issued that token and 3rd party API in question validates this fact.
Currently, issuer field gets generated as com.tddfellow. According to this standard, it has to be https://tddfellow.com.
Additionally, we have to support old versions of mobile clients for next 6 months, that are validating issuer to be com.tddfellow, we can not change them as they are already installed on users’ mobile devices.
Solution: bump the version of our API from v1 to v2 and use v1 for integration with old mobile clients and use v2 for integration with 3rd party API and all new clients.
Easiest solution using passed in apiVersion from the Main program and switch on it being old or new in the use case to determine which issuer to generate:
123456789101112131415161718192021
// Main.ktSpark.get("/api/v1/issueVerificationToken"){request,response->issueVerificationTokenEndpoint.issueVerificationToken(deviceId=request.queryParams("deviceId"),phoneNumber=request.queryParams("phoneNumber"),// here we are passing "old" version to the endpointapiVersion="v1")}Spark.get("/api/v2/issueVerificationToken"){request,response->issueVerificationTokenEndpoint.issueVerificationToken(deviceId=request.queryParams("deviceId"),phoneNumber=request.queryParams("phoneNumber"),// here we are passing "new" version to the endpointapiVersion="v2")}
And the endpoint just passes this value through to the use case:
12345678910111213141516171819
// ApiEndpoints/IssueVerificationToken/Endpoint.ktfunissueVerificationToken(deviceId:String,phoneNumber:String,apiVersion:String):IssueVerificationTokenEndpointResponse{valissueVerificationToken=useCase.issueVerificationToken(deviceId=deviceId,phoneNumber=phoneNumber,// here we are passing API version throughapiVersion=apiVersion)returnIssueVerificationTokenEndpointResponse(issuer=issueVerificationToken.issuer,token=issueVerificationToken.secureToken)}
use case probably should not have any knowledge of apiVersion, since APIs is not our domain, it is just a delivery mechanism
If we were to pass some object, like TokenIssuer, it would probably be more appropriate to have use case know of it. Let’s try to refactor:
Refactoring if Statement Using Polymorphism
First, let’s start passing in the token issuer in the routing:
123456789101112131415161718192021
// Main.ktSpark.get("/api/v1/issueVerificationToken"){request,response->issueVerificationTokenEndpoint.issueVerificationToken(deviceId=request.queryParams("deviceId"),phoneNumber=request.queryParams("phoneNumber"),// here we pass a specific object now for old token issuer:tokenIssuer=OldTokenIssuer())}Spark.get("/api/v2/issueVerificationToken"){request,response->issueVerificationTokenEndpoint.issueVerificationToken(deviceId=request.queryParams("deviceId"),phoneNumber=request.queryParams("phoneNumber"),// here we pass an object that returns URL according to the standardtokenIssuer=UrlTokenIssuer())}
And this is how TokenIssuer and its derivatives are looking like:
As you might guess, endpoint just passes this object through to the use case. And the use case itself just calls getName() on it when generating issuer:
12345678910111213141516
// IssueVerificationToken/UseCase.ktfunissueVerificationToken(deviceId:String,phoneNumber:String,tokenIssuer:TokenIssuer):VerificationToken{valverificationToken=VerificationToken(// here is how much simpler it becomesissuer=tokenIssuer.getName(),deviceId=deviceId,phoneNumber=phoneNumber,secureToken=secureTokenSource.generateToken())verificationTokenGateway.persist(verificationToken)returnverificationToken}
This code may be refactored further so that even Endpoint class will not have to know about tokenIssuer and pass it through. I will leave that as an exercise to you, my dear reader.
You would not want to miss next articles on this tech blog, we still have a lot to talk about:
Continuous Integration and Continuous Delivery - importance of not impeding others,
Open-Closed Principle - changing behavior by adding new code,
Triangulation technique in Test-Driven Development - overlooking this technique might cause one fail at doing TDD,
Mutational Testing, “Build Your Own Testing Framework” series, Test-Driven Development screencasts and so much more!
Thanks!
Thank you for reading, my dear reader. If you liked it, please share this article on social networks and follow me on twitter: @tdd_fellow.
If you have any questions or feedback for me, don’t hesitate to reach me out on Twitter: @tdd_fellow.