From my personal experience, for any project, I recommend you to always go through the service layer, in order to access its data, rather than accessing the data layer directly.
From the data layer, you usually cannot guarantee an object is returned, therefore all references are returned as nullable. Adding a service facade over this enables you to introduce helper methods, which e.g. throw an exception when a record is not found, and otherwise returning always a non-null object.
Other reasoning is the ability to easily change the interface of the data access layer. Since the usage would only be limited to the Service-2, making adjustments to the data layer's API is a very easy task.
Also, requiring the service as a facade can make you program less methods on your data access object. E.g. you could only introduce returning a List
of objects on the repository, accepting a Set
of identifiers, and the service facade would provide convenient methods to retrieve multiple or a single record:
interface SomeRepository {
public @NotNull List<Object> getSomeObjectsByIds(@NotNull Set<Long> ids);
}
class SomeService {
private final SomeRepository someRepository;
// constructor omitted
public @Nullable Object getSomeObjectById(long id) {
return someRepository.getSomeObjectsByIds(setOf(id)).firstOrNull();
}
public @NotNull List<Object> getSomeObjectsByIds(@NotNull Set<Long> ids) {
return someRepository.getSomeObjectsByIds(ids);
}
}
Besides this, you could also introduce caching on the service layer, therefore guaranteeing a single caching mechanism, without polluting the raw DAL with the cache mechanism.