3. Factories
When we discussed creation of entity instances I
mentioned that I prefer a static factory method to a direct use of a
constructor. In most cases, either of those two options will be all you
need. However, in some cases the creational logic for an entity might be
complex, in which case implementing a factory as a separate class might
be the best approach.
In most cases, implementing a factory as a separate
class is not very different from the static factory method approach. For
example, the following factory class is functionally equivalent to the
static factory method we created earlier:
public class AccountFactory {
private static IdentityGenerator<Long> s_idGen =
SequenceGenerator.create("account.id", 20);
public Account createAccount(Customer customer,
String description,
Currency currency) {
return new Account(s_idGen.generateIdentity(),
customer.getId(),
description,
new Money(0, currency));
}
}
However, the fact that the factory is now a separate
class opens up some interesting possibilities. For example, we could get
rid of the hardcoded SequenceGenerator dependency and use Spring to configure the factory instance:
public class AccountFactory {
private IdentityGenerator<Long> m_idGen;
public AccountFactory(IdentityGenerator<Long> idGen) {
m_idGen = idGen;
}
public Account createAccount(Customer customer,
String description,
Currency currency) {
return new Account(m_idGen.generateIdentity(),
customer.getId(),
description,
new Money(0, currency));
}
}
The following Spring configuration could then be used to wire the AccountFactory instance:
<bean id="accountFactory" class="domain.AccountFactory">
<constructor-arg>
<bean class="c.s.c.identity.sequence.SequenceGenerator">
<constructor-arg value="account.id"/>
<constructor-arg value="20"/>
</bean>
</constructor-arg>
</bean>
That way you can let Spring manage dependencies and
will be able to provide a fake or mock for the identity generator within
unit tests, which alone might be a good enough reason to take this
extra step.
4. Repositories
The factories are useful when we need to create new
instances of domain objects, but in many cases we simply need to
retrieve the existing instances. In order to accomplish that, we can use
a repository.
According to Domain Driven Design,
a repository can be thought of as an unlimited, memory-backed object
store containing all objects of a specific type. If that definition
reminds you of a Coherence cache, you are not alone-a Coherence cache is
a great object repository indeed.
Ideally, the repository interface should be
completely independent of the underlying infrastructure and should be
expressed in terms of domain objects and their attributes only. For
example, we could define AccountRepository like this:
public interface AccountRepository {
Account getAccount(Long id);
Collection<Account> getAccounts(Collection<Long> accountIds);
void save(Account account);
}
As you can see, the interface is completely Coherence
agnostic, and can be implemented for pretty much any data source. A
Coherence implementation might look like this:
public class CoherenceAccountRepository
implements AccountRepository {
private static final NamedCache s_accounts = CacheFactory.getCache("accounts");
public Account getAccount(Long id) {
return (Account) s_accounts.get(id);
}
public Collection<Account> getAccounts( Collection<Long> accountIds) {
return (Collection<Account>)
s_accounts.getAll(accountIds).values();
}
public void save(Account account) {
s_accounts.put(account.getId(), account);
}
}
What you will quickly notice if you start
implementing repositories this way is that basic CRUD operations tend to
repeat within every repository implementation, so it makes sense to
pull them up into an abstract base class in order to avoid implementing them over and over again:
public abstract class AbstractCoherenceRepository<K, V extends Entity<K>> {
public abstract NamedCache getCache();
public V get(K key) {
return (V) getCache().get(key);
repository, domain model building blocksabstract base class}
public Collection<V> getAll(Collection<K> keys) {
return getCache().getAll(keys).values();
}
public void save(V value) {
getCache().putAll(
Collections.singletonMap(value.getId(), value));
}
public void saveAll(Collection<V> values) {
Map batch = new HashMap(values.size());
for (V value : values) {
batch.put(value.getId(), value);
}
getCache().putAll(batch);
}
}
This abstract base class for our repository
implementations uses generics to specify the key and value type for the
cache that the repository is used to access, and constrains value type
to the types that implement the Entity interface we defined earlier. This allows us to implement the save and saveAll methods as mentioned earlier, because we can obtain the cache key for any entity by calling its getId method.
NamedCache.put versus NamedCache.putAll
One thing to note is the implementation of the save method. While I could've just made a simple call to a NamedCache.put method, I have chosen to use putAll instead, in order to improve performance by eliminating one network call.
The reason for this is that the NamedCache.put method strictly follows the contract defined by the Map
interface and returns the old value from the cache. This is fine when
you are accessing a map in-process or need the old value, but in this
case neither is true and using put would simply increase the latency.
Our CoherenceAccountRepository implementation now simply becomes:
public class CoherenceOrderRepository
extends AbstractCoherenceRepository<Long, Account>
implements AccountRepository {
private static final NamedCache m_accounts = CacheFactory.getCache("accounts");
public NamedCache getCache() {
return m_accounts;
}
public Account getAccount(Long id) {
return super.get(id);
}
public Collection<Account> getAccounts( Collection<Long> accountIds) {
return super.getAll(accountIds);
}
}
This concludes our coverage of different types of
data objects within a domain model and how to map them to Coherence
caches. For the most part, you should now know enough to be able to
implement your data objects and put them into the cache.
One thing you might've noticed is that, aside from
the repository implementation, none of the domain classes we implemented
thus far have any dependency on or knowledge of Coherence, despite the
somewhat leaky repository abstraction. This is an example of what DDD
calls persistence ignorance (PI) and is extremely important as it allows us to unit test our domain model objects in isolation.
For the most part, you can
use the domain objects we implemented in this section without any
modifications. However, there are several important considerations that
we haven't discussed yet that you need to understand in order to
implement your data objects (entities and aggregates) in an optimal way.