Terminology: I'll refer to the language construct interface
as interface, and to the interface of a type or object as surface (for a lack of a better term).
Loose coupling can be achieved by having an object depend on an abstraction instead of a concrete type.
Correct.
This allows for loose coupling for two main reasons: 1- abstractions are less likely to change than concrete types, which means the dependent code is less likely to break. 2- different concrete types can be used at runtime, because they all fit the abstraction. New concrete types can also be added later with no need to alter the existing dependent code.
Not quite correct. Current languages do not generally anticipate that an abstraction will change (although there are some design patterns to handle that). Separating specifics from general things is abstraction. This is usually done by some layer of abstraction. This layer can be changed to some other specifics without breaking code that builds upon this abstraction – loose coupling is achieved. Non-OOP example: A sort
routine might be changed from Quicksort in version 1 to Tim Sort in version 2. Code that only depends on the result being sorted (i.e. builds upon the sort
abstraction) is therefore decoupled from the actual sorting implementation.
What I termed surface above is the general part of an abstraction. It now happens in OOP that one object must sometimes support multiple abstractions. A not-quite optimal example: Java's java.util.LinkedList
supports both the List
interface which is about the “ordered, indexable collection” abstraction, and supports the Queue
interface which (in rough terms) is about the “FIFO” abstraction.
How can an object support multiple abstractions?
C++ doesn't have interfaces, but it has multiple inheritance, virtual methods, and abstract classes. An abstraction can then be defined as an abstract class (i.e. a class that cannot be immediately instantiated) that declares, but not defines virtual methods. Classes that implement the specifics of an abstraction can then inherit from that abstract class and implement the required virtual methods.
The problem here is that multiple inheritance can lead to the diamond problem, where the order in which classes are searched for a method implementation (MRO: method resolution order) can lead to “contradictions”. There are two responses to this:
Define a sane order and reject those orders that can't be sensibly linearized. The C3 MRO is fairly sensible and works well. It was published 1996.
Take the easy route and reject multiple inheritance throughout.
Java took the latter option and chose single behavioral inheritance. However, we still need the ability of an object to support multiple abstractions. Therefore, interfaces have to be used which do not support method definitions, only declarations.
The result is that the MRO is obvious (just look at each superclass in order), and that our object can have multiple surfaces for any number of abstractions.
This turns out to be rather unsatisfactory, because quite often a bit of behavior is part of the surface. Consider an Comparable
interface:
interface Comparable<T> {
public int cmp(T that);
public boolean lt(T that); // less than
public boolean le(T that); // less than or equal
public boolean eq(T that); // equal
public boolean ne(T that); // not equal
public boolean ge(T that); // greater than or equal
public boolean gt(T that); // greater than
}
This is very user-friendly (a nice API with many convenient methods), but tedious to implement. We would like the interface to only include cmp
, and implement the other methods automatically in terms of that one required method. Mixins, but more importantly Traits [1],[ 2] solve this problem without falling into the traps of multiple inheritance.
This is done by defining a trait composition so that the traits don't actually end up taking part in the MRO – instead the defined methods are composed into the implementing class.
The Comparable
interface could be expressed in Scala as
trait Comparable[T] {
def cmp(that: T): Int
def lt(that: T): Boolean = this.cmp(that) < 0
def le(that: T): Boolean = this.cmp(that) <= 0
...
}
When a class then uses that trait, the other methods get added to the class definition:
// "extends" isn't different from Java's "implements" in this case
case class Inty(val x: Int) extends Comparable[Inty] {
override def cmp(that: Inty) = this.x - that.x
// lt etc. get added automatically
}
So Inty(4) cmp Inty(6)
would be -2
and Inty(4) lt Inty(6)
would be true
.
Many languages have some support for traits, and any language that has a “Metaobject Protocol (MOP)” can have traits added to it. The recent Java 8 update added default methods which are similar to traits (methods in interfaces can have fallback implementations so that it's optional for implementing classes to implement these methods).
Unfortunately, traits are a fairly recent invention (2002), and are thus fairly rare in the larger mainstream languages.
Are Domain Services merely an Interface which exist to provide a layer
of abstraction between the Infrastructure Layer and your Model? ie:
Repositories + HashingServices, etc.
Domain services responsibilities include several things. The most obvious is housing logic that doesn't fit into a single entity. For example, you may need to authorize a refund for a certain purchase, but to complete the operation you need data from the Purchase
entity, Customer
entity, CustomerMembership
entity.
Domain services also my provide operations needed by the domain to complete its functionality such as PasswordEncryptionService
, but the implementation of this service will reside in the infrastructure layer since it will mostly be a technical solution.
Infrastructure services are services which so an infrastructure operation such as opening a network connection, copy file from file system, talk to an external web service or talk to database.
Application services are the implementation of a use case in the application you are building. If you are cancelling a flight reservation you would:
- Query the database for the Reservation object.
- invoke Reservation->cancel.
- Save object to DB.
The application layer is the client of the domain. The domain has no idea what your use case is. It just exposes the functionality through its aggregates and domain services. The application layer however mirrors what you are trying to achieve by orchestrating the domain and infrastructure layers.
I mentioned having an Application Service which looks like this:
Access/Application/Services/UserService::CreateUser(string name,
string email, etc): User
The method signature accepts primitive data type arguments and returns
a new User Entity (not a DTO!).
PHP might not be the best place to start learning about DDD since many of the PHP frameworks out there (Laravel, Symfony, Zend,..etc) tend to promote RAD. They are focused more on CRUD and translating forms to entities. CRUD != DDD
Your presentation layer should be responsible for reading the form inputs from the request object and invoking the correct application service.
The application service will create the user and invoke the User repository to save the new user. You may optionally return a DTO of the user back to the presentation layer.
How should separate modules handle unidirectional relationships.
Should module A reference module B's application layer (as it's doing
now) or the infrastructure implementation (via separated interface)?
The word module in DDD lingo has a different meaning than what you are describing. A module should house related concepts. For example, an order module in the domain layer could house the Order aggregate, OrderItem entity, OrderRepositoryInterface and MaxOrderValidationService.
An Order module in the application layer could house the OrderApplicationServie, CreateOrderCommand and OrderDto.
If you are talking about layers then each layer should preferably depend on interfaces of other layers whenever possible. The presentation layer should depend on interfaces of the application layer. Application layer should reference interfaces of the repositories or domain services.
I personally don't create interfaces for entities and value objects coz I believe interfaces should be related to a behavior, but YMMV :)
Do Application Layer Services require a Separate Interface? If the
answer is yes then where should they be located?
It depends :)
for complex applications I build interfaces coz we apply rigorous unit, integration and acceptance testing. Loose coupling is key here and the interfaces are in the same layer (application layer).
For simple app I build against the app services directly.
Best Answer
Well, well, let's get something that is bothering me out of the way first...
OK, now that I took it out of me it, let's get to the point!
Short Answer
The short answer to your question is yes, it does make sense to make entities depend on abstractions in some occasions. Once you translate your Domain Design to an Object-Oriented library, different rules come into play and, by these rules, there are no "Entities"... it's just values, abstractions and behaviors, so anything is allowed (as long as it makes your life easier, of course)!
Long Answer
Object-Oriented Programming and Domain-Driven Design are not the same thing, nor are they each other's "blessing". They are distinct philosophies, dealing with distinct problem domains. Entities represent something almost entirely philosophical in Domain-Driven Design, as do aggregate roots, value objects, etc. Putting them all together using Object-Oriented Programming is simply using a means (one of many, really) to achieve an end.
Entities happen to be representable as state-holding units (what we call classes) and these can be aggregated in other such units to enforce a desired consistency between otherwise unrelated elements (or elements related to each other only through philosophical descriptions and meanings). In that sense, a Student and a Teacher are expected to be found close to some School or Department entity conceptually. These relations are not always straightforward and, more often than not, they are not unique.
Using Object-Oriented Programming techniques helps you define your domain model and describe the state of all entities, as well as the relations between different entities. The point here is that you must remember that when talking about O-OP and D-DD, you are actually talking about two different things and, therefore, there is no "natural" way to translate or exchange concepts or ideas between the two. That is to say, just because Object-Oriented Programming thrives on abstractions and is heavily based on contracts (interfaces), it is not necessary that D-DD can also benefit from these concepts (all the more as D-DD speaks of contracts as the relations between concrete elements of reality). Think about, for example, how useful an abstraction of a Point with 2 coordinates would be, in almost any domain. What else can play the role of a Point besides... a Point? Why make a Point an interface, then?
Because...
Entities have state but they can also have behavior. And while state is tangibly defined, behavior can always be abstracted away! This is where interfaces come into play! Let's get back to your original question and thought:
If you think of a Student entity as, for example, a human with a Name, an ID, an Age, a Sex, the Courses they are enrolled in, etc. indeed, it does not. Then you don't need O-OP to achieve anything. You can create an elaborate database and get away with equally elaborate queries about everything.
But how about thinking of a Student entity as a dynamic element with behavior. What if the Student object is tasked to give you information useful for your problem, rather than act as a data container? What may not appear very intuitive right now, it just might when you consider some use cases. Compare, for example, the following two definitions (C#-like code):
If the
StudentEntity
is your implementation, you need a number ofService
objects to perform, for example, queries with respect to the StudentEntity. Compare, then, the following two excerpts:Excerpt 1 is based on the StudentEntity class to retrieve information and make "judgments".
Excerpt 2 simply relies on asking the interface, so the unknown implementation behind the interface answers.
How well does the first excerpt fare in comparison to the second in terms of maintainability? Well, frankly, not too well. If the law changes, you have to inspect ALL ### lines to ensure that the new legislation is now correctly represented. Compare that to just focusing on ThaiStudent and simply correcting 1-2 lines of code, so they can keep using it in Thailand after your latest update patch!
OK, I have to admit that the example above is extraordinarily detached from reality, but the purpose is to make it clear that interfaces are just a means to an end (i.e. to abstract behavior) and it doesn't matter if you employ them to abstract away the specifics of an OS service, a web-request or the behavior of a Domain Entity. They always have a potential to be useful!
In short, if you want a truly useful (and far from anemic) Domain Model, you can "drag" your rules, limitations, interaction constraints, etc. inside the Domain elements, as behavior (i.e. methods). "Blessing" some of your Domain Entities with behavior will lead you to wonder whether these could be enacted differently based on circumstances. So, in these cases, if you want to stay as clear as possible of
if{...} else if {...} else if {...}
chaos zones, you would create different implementations of domain entity interfaces for your domain entities, at least where it makes sense to do so.Just because, in the real world, we (hopefully) don't discriminate between students of different nationalities, let alone we don't really ask students whether they are of legal age and just trust their answers, in the O-OP world, tasking the relevant entities with the management of such complications and details effectively hides them from us and lets us focus on developing the rest of the code with somewhat less of an information burden.
Remember, we borrowed ideas from O-OP and applied them to D-DD. It is was totally unnecessary but it offered a layer of flexibility that has the real potential to ease maintenance in the long run!