Patterns/Creational/Factory
Please note that this post is kind of essay to gather info for presentation and mentoring of younger developers.
Patterns/Creational/Factory
Factory is one of the most known patterns. Probably you already added to your projects IoC container that manages the creation of services, endpoints, and data access objects. And yes, you will use factory in most of your projects, but probably you will not write them. So, do you need it in your domain logic?
Purpose:
- Hide the logic of object creation.
- Use caching of objects.
- Adding proxy or decorators.
- Decouple client from object creation. (You want to use polymorphism?)
Problems:
- Probably you already have some Dependency Injection framework that manages object creation, do you really need to implement the Factory pattern?
- In practice, I don’t see a lot of developers who actively used polymorphism, so it may be some kind of over-engineering.
Basic implementation:
interface Invoice { /* ... */ }
enum InvoiceType {INTERNAL, EXTERNAL}
class InvoiceFactory {
public Invoice createInvoice(InvoiceType type) {
return switch (type) {
case INTERNAL -> new InternalInvoice();
case EXTERNAL -> new ExternalInvoice();
default -> throw new IllegalArgumentException("Unknown invoice type: " + type);
};
}
}
Modern implementation could be using Supplier:
interface Invoice { /* ... */ }
enum InvoiceType { INTERNAL, EXTERNAL }
private final Map<InvoiceType, Supplier<Invoice>> invoiceProviders = Map.of(
InvoiceType.INTERNAL, InternalInvoice::new,
InvoiceType.EXTERNAL, ExternalInvoice::new
);
Invoice invoice = invoiceProviders.get(type);
However, it is just a way to remove switch/if to a map. I would rather don’t recommend you to call it factory, and put it to the client class. Because it is not extendable and do not hide the logic of object creation.
You may also put factory method to the spring configuration:
interface Invoice { /* ... */ }
enum InvoiceType { INTERNAL, EXTERNAL }
class InternalInvoice implements Invoice { /* ... */ }
class ExternalInvoice implements Invoice { /* ... */ }
@Configuration
class AppConfig {
@Bean
@Scope("prototype")
public Invoice internalInvoice(InvoiceType type) {
switch (type) {
case INTERNAL: return new InternalInvoice();
case EXTERNAL: return new ExternalInvoice();
default: throw new IllegalArgumentException("Unknown invoice type: " + type);
}
}
}
class InvoiceService {
private final ObjectProvider<Invoice> invoiceFactory;
public void process(InvoiceType type) {
log.info("Processing {}, {}", type, this.invoiceFactory.getObject(type));
}
}
It seems that you coworkers will be shocked that you get domain objects from spring and use prototype scope. Just don’t mention where you got that idea.
Use Cases
Context or Prefetched Data
Let’s assume that you have service with many command methods to manage Orders. And that method required having some context from product and client services.
@Service
@RequiredArgsConstructor
class OrderService {
private final OrderRepository orderRepository;
private final ClientService clientService;
private final ProductService productService;
public void createOrder(CreateOrderCommand command) { /* ... */ }
public void changeOrder(ChangeOrderCommand command) { /* ... */ }
public void cancelOrder(CancelOrderCommand command) { /* ... */ }
public void markAsDoneOrder(MarkAsDoneOrderCommand command) { /* ... */ }
public void archiveOrder(ArchiveOrderCommand command) { /* ... */ }
}
cancel
Generally, all of them will make same work:
Set<UUID> productIds order.getItems().map(OrderItem::getProductId).collect(toSet());
Map<UUID, Product> products = productService.getProducts(productIds);
Client client = clientService.getClient(order.getClientId());
In order to do not repeat this code in every method, you probably will introduce a context object:
@Value
class OrderContext {
private final Map<UUID, Product> products;
private final Client client;
}
It also may code more extendable if you pass that context to the method arguments:
new Order(command, context);
order.cancel(command, context);
//...
Now, to minimize the amount of code in the OrderService, you may want to extract the code that creates the context to the separate factory class:
import java.util.Collection;
@Component
@RequiredArgsConstructor
class OrderContextFactory {
private final ProductService productService;
private final ClientService clientService;
public OrderContext create(OrderContextInput order) {
Map<UUID, Product> products = productService.getProducts(order.getProductIds());
Client client = clientService.getClient(order.getClientId());
return new OrderContext(products, client);
}
public interface OrderContextInput {
Collection<UUID> getProductIds();
UUID getClientId();
}
}
And use it in the OrderService:
@Service
@RequiredArgsConstructor
class OrderService {
private final OrderRepository orderRepository;
private final OrderContextFactory orderContextFactory;
public void createOrder(CreateOrderCommand command) {
OrderContext context = orderContextFactory.create(command);
Order order = new Order(command, context);
orderRepository.save(order);
}
public void changeOrder(ChangeOrderCommand command) { /* ... */ }
public void cancelOrder(CancelOrderCommand command) { /* ... */ }
public void markAsDoneOrder(MarkAsDoneOrderCommand command) { /* ... */ }
public void archiveOrder(ArchiveOrderCommand command) { /* ... */ }
}
Probably it is way to go if your service becomes too big, but I would rather not suggest it as a default practice, when you start development and even may don’t know what is needed.
One additional case is when you have processing in different classes like CreateOrderCommandHandler, ChangeOrderCommandHandler, etc. Then to extract the context creation to the factory seems to be a good idea.
Reusing Objects
Assume that you have the Product Specification entity that is immutable and can be reused in different places of your application. Hence, you may create a factory that will cache the objects:
class ProductSpecificationFactory {
private final Map<ProductSpecificationSelector, ProductSpecification> cache = new ConcurrentHashMap<>();
private final ProductSpecificationRepository repository;
public ProductSpecification get(ProductSpecificationSelector selector) {
return cache.computeIfAbsent(productId, repository::findBySelector);
}
}
The problem is that normally useless because you may use the cache layer in repository or some kind of aspects.
Behavior Modification
Let’s assume that you have some
import java.lang.reflect.Proxy;
import java.util.UUID;
import java.util.concurrent.locks.ReentrantLock;
interface Invoice { /* ... */ }
@Component
class InvoiceFactory {
Map<UUID, ReentrantLock> locks = new ConcurrentHashMap<>();
@SneakyThrows
public Invoice createInvoice() {
Invoice invoice = new InvoiceImpl();
Proxy.newProxyInstance(Invoice.class.getClassLoader(), new Class[]{Invoice.class}, (proxy, method, args) -> {
ReentrantLock lock = locks.computeIfAbsent(invoice.getId(), id -> new ReentrantLock());
try {
lock.lock();
return method.invoke(proxy, args);
} finally {
lock.unlock();
}
});
}
}
Polymorphism
Seems to be a most legit use case of the Factory pattern, but not so common.
abstract class Invoice { /* ... */ }
class InternalInvoice extends Invoice { /* ... */ }
class ExternalInvoice extends Invoice { /* ... */ }
class InvoiceFactory {
public Invoice createInvoice(InvoiceType type) {
return switch (type) {
case INTERNAL -> new InternalInvoice();
case EXTERNAL -> new ExternalInvoice();
default -> throw new IllegalArgumentException("Unknown invoice type: " + type);
};
}
}
Probably there will be different behavior because of tax calculation, hence polymorphism is needed. Even if you want to extract that part in future to the separate class, like TaxCalculator or some kind of strategy, you will need to instantiate it with polymorphism, hence factory still will be needed.
However, in most cases, I saw that developers just used plain old if statements to make calculation fork based on the type.
class TaxCalculationService {
public void calculate(Invoice invoice) {
if (invoice.getType() == InvoiceType.INTERNAL) {
// ...
} else if (invoice.getType() == InvoiceType.EXTERNAL) {
// ...
} else {
throw new IllegalArgumentException("Unknown invoice type: " + invoice);
}
}
}
Compositions
Systems that use factories are often going deeper to the factories, and start use factories of factories and abstract factories.
Hence, let’s touch that topic a little bit.
- Factory of factories, probably commonly is just your IoC container, like Spring or Guice. So it could be good to say stop at some point of that rabbit hole.
- Abstract factories—it is a way to create a family of objects. IoC containers may provide you profiles if that family is different because of configuration. Otherwise, it may be a case for that approach. But honestly, I never saw that in practice.