Template pattern with real-world example
In this tutorial, we will solve the a real-world problem and solve it with the template pattern.
The real-world business problem that I had in my real job
We had one microservice that just worked as a proxy to Salesforce.

Our proxy service had multiple endpoints such as getCarsByCriteria
, getPersonsByCriteria
, getCarsWithExpiredInsurance
, etc. Inside the proxy service, we transform the API request into the Salesforce queries(similar to the SQL queries), call Salesforce through HTTP, and convert that result to the proxy service response. So simple.
When I joined the company, that service was already written and it was in terrible shape. The code inside the proxy service looked like this:
public class CarService {
private SalesForceClient salesForceClient;
public List<CarResponse> getCarsWithExpiredInsurance() {
salesForceClient.get("select id,car_name__c,car_brand from car where expired = true");
//mapping to the reponseList
return reponseList;
}
public List<CarResponse> getAllCars() {
salesForceClient.get("select id,car_name__c,car_brand from car");
//mapping to the reponseList
return reponseList;
}
public List<CarResponse> getAllCarsWithCertainBrand(String brand) {
salesForceClient.get("select id,car_name__c,car_brand from car where brand = '" + brand + "'");
//mapping to the reponseList
return reponseList;
}
}
Problems of this approach
As you can see, pure and simple string concatenation. Problems are:
- With every new query, there was no reusing of old code. When you want to put strings inside
where
part, strings are surrounded by'
but when you put dates, they should not be surrounded by'
. There are a bunch of other small things that you cannot reuse from query to query. - There is no structure at all. When adding a new query that needs to fetch a person and its cars, developers were uncertain if they should add that feature to
CarService
,PersonService
, or maybe create a new service. - If you want to add pagination to all queries, you need to append that in all queries or somehow wrap all services with some magic to support pagination.
- There is no way to unit test only query creation because
CarService
breaks the Single Responsibility principle.CarService
creates a query and then calls the Salesforce client. Those two things need to be separated.
Resolve problem step by step
Firstly, I helped one of my colleagues apply builder and composite patterns to this string concatenation. The result of applying those patterns is that now we can create queries in the following fashion:
QueryBuilder queryBuilder = QueryBuilder.builder()
.select(Arrays.asList("id", "name", "age"))
.from("persons")
.where(
new NotEqualsCondition("id", 5)
.and(new EqualsCondition("name","Rick"))
.and(new LikeCondition("address", "Street%"))
.and(new HigherEqualsCondition("age",18))
.and(new EqualsCondition("hairColor", "Black"))
.or(new InCondition("city",String.join(",", Arrays.asList("Manchester", "London"))))
.or(new InCondition("city",String.join(",", Arrays.asList("Paris", "Nice"))).not())
).groupBy("country").orderBy("creationDate").limit(10).build();
QueryBuilder
class is a result of the builder pattern and all of those *Condition
classes are consequences of the composite pattern.
This looks much better than string concatenation. But still, we have a problem that for every new query, we need to create a new method, put that method in some service, there is still no structure between queries, etc.
Now, let’s think. Every query has a similar template. It must contain select
and from
parts and optional parts are where
, group by
, and order by
. Every query should override those parts specifically.
Template pattern applied
Let’s see the UML diagram and the code and then we will explain everything:

Coding time
Let’s create that template first:
public abstract class QueryCreator {
public abstract List<String> select();
public abstract String from();
public abstract <T> Condition where(T request);
public String groupBy() {
return null;
}
public <T> String createQuery(T request) {
QueryBuilder.Builder builder = QueryBuilder
.builder()
.select(select())
.from(from())
.where(where(request))
.groupBy(groupBy());
String soql = builder.build().getQuery();
return soql;
}
}
and let’s implement that template for one concrete query(getAllCarsWithCertainBrand
for example)
public class GetAllCarsWithCertainBrand extends QueryCreator {
@Override
public List<String> select() {
return asList("id", "car_name__c","car_brand");
}
@Override
public String from() {
return "car";
}
@Override
public <T> Condition where(T request) {
GetAllCarsWithCertainBrandRequest req = (GetAllCarsWithCertainBrandRequest) request;
return new EqualsCondition("brand",req.getBrand());
}
}
This is how we use our creator:
public class CarService {
private GetAllCarsWithCertainBrand getAllCarsWithCertainBrand;
public List<CarResponse> getAllCarsWithCertainBrand(GetAllCarsWithCertainBrandRequest request) {
salesForceClient.get(getAllCarsWithCertainBrand.createQuery(request));
//mapping to the reponseList
return reponseList;
}
}
Important to notice is that abstract template logic is inside the QueryCreator
class and the specific implementation of the template is in concrete classes that extend QueryCreator
(in our example we have one query GetAllCarsWithCertainBrand
). In our example, template is that every time we need to take the result of the select
, from
, where
, and groupBy
methods and combine those(using builder pattern) into the result. But every query has its implementation of those select
, from
, where
, and groupBy
methods. Responsibilities:
- Call salesforce is now inside
CarService
. - Defining templates for all queries is inside
QueryCreator
- Create a specific query logic is inside concrete classes that extend
QueryCreator
(for exampleGetAllCarsWithCertainBrand
)
which means that we satisfy the Single responsibility principle.
Conclusion
All listed problems are solved with this approach:
- You implemented salesforce syntax inside your builder so you don’t need to implement that again and again.
- There is a simple structure(our template). When you want to support a new query, create a new subclass of
QueryCreator
and that’s it. - Since now the responsibility of
QueryCreator
is to create a query, you can create a unit test for it. - If you want to support pagination for all queries, you can do that inside
createQuery
method. You can also expose oneorderBy
method fromQueryCreator
class and in every query you can implement a different order. That is one place. You don’t need to duplicate pagination code across multiple classes.
Further reading
If you want to learn more about design patterns and best practices, here are the great resources:
- Patterns of Enterprise Application Architecture by Martin Fowler
- Domain-Driven Design: Tackling Complexity in the Heart of Software by Eric Evans. “Blue” book, that is along with the “red” book(Vernon’s book) the best material regarding the DDD topic. Note that those two books are very hard to understand(don’t be frustrated and quit because you cannot understand things easily)
- Implementing Domain-Driven Design by Vaughn Vernon
- Microservices Patterns by Chris Richardson. Great book to learn about Microservices patterns theory and how to implement them using Java
- Hexagonal architecture using Spring and Java. This is the article. Take a look at it and if you like it there is a short book linked at the bottom of the article. I went through the book and it is awesome!
- Patterns and Best Practices category on my website