-
-
Save christianromney/6596842 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
Hi, I'm Reservation. | |
I dont need an interface because I am a carrier of data. | |
Like a good carrier, I can be instantiated in any layer and used | |
in any other layer. | |
If you need to do stuff with me you should know to instantiate the correct | |
service and pass me to it. | |
Makes sense too since I you might need other classes to instantiate service | |
and I dont want to be dependent on them since they may not be used across | |
all layers. | |
*/ | |
@serializable | |
class Reservation { | |
// Simple instantiation. I can be instantiated by the ORM framework or the | |
// presentation layer or the integration service. | |
public Reservation() {...} | |
public Reservation(ITransactionGateway backend) {...} | |
// Same getters as before... | |
public IList<IGuest> getGuests() {...} | |
public Duration getLength() {...} | |
public Amount getBalance() {...} | |
public Amount getTotal() {...} | |
// All the interesting work is done by a strategy which depends on the | |
// layer I am in. If I am in the ORM, the strategy would retrieve me from the DB. | |
// If I am in the integration layer the strategy would retrieve me from the back end. | |
// If I am in the service layer there would be services that would do things to me | |
// If I am in the presentation layer there would be controllers populating me from a form. | |
// If I am in the bla layer.. i think you must have got the idea by now.. | |
} | |
// Of course this could be called Reservation Manager... | |
public interface ReservationManager { | |
bool cancel(IReservation reservation); | |
PaymentStatus payInFull(IReservation reservation, IPaymentMethod method); | |
PaymentStatus pay(IReservation reservation, IPaymentMethod method, Amount amount); | |
} | |
/* | |
Hi, we haven't met, you probably don't need to know me personally since | |
you know my interface already. All you need to do is to ask for a reservation manager | |
and I would be plugged into you by the Dependency Injection framework if I am in the | |
same layer as you. If you are in the presentation layer there is a proxy who would interface | |
with me over the wires. But you don't need to bother about all that. The deployment packaging | |
would do the needful. | |
*/ | |
class ReservationManagerImpl implements ReservationManager { | |
// Dependency injection for the cashier... | |
public ReservationManager(ITransactionGateway backend) {...} | |
public PaymentStatus payInFull(IReservation reservation, IPaymentMethod method) { | |
/* | |
Do I have to think of everything? Thank goodness | |
I know how to get the balance of the reservation. | |
*/ | |
return pay(reservation, method, reservation.getBalance()); | |
} | |
/* | |
Notice that the reservation has most of the data. | |
I am the backend and I know how to transact. If there are multiple | |
dudes who know how to transact, then I can orchestrate interactions between | |
those and provide wrappers around them for horizontal services such as transactions, | |
security, caching, logging, auditing etc. | |
Also, remember the presentation layer's job is not to orchestrate me. Its job is to | |
populate the model based on the view. So don't think that it would steal my thunder and I | |
have become relegated to a middle management. No way! | |
*/ | |
public PaymentStatus pay(IReservation reservation, IPaymentMethod method, Amount amount) { | |
return backend.applyPayment(reservation, method, amount); | |
} | |
} | |
/* | |
A few interesting things to note about | |
the anemic model version: | |
1. No new functionality has been added, but we | |
have doubled the number of interfaces and classes | |
(and, presumably, tests). - *Not true. No interfaces for | |
"carrier objects" No tests for carrier objects (I dont need to | |
test that the thing I have set is the one I am getting)* | |
2. We have increased the complexity of the interactions | |
by introducing an additional actor (ReservationManager). - Nope. | |
We have simplified the interaction because | |
ReservationManager is a strategy that is injectable where needed. | |
3. The interesting methods (moved to IKnowHow) have also | |
become more complex since they each now require an | |
additional IReservation parameter. e.g. | |
PaymentStatus pay(IPaymentMethod method, Amount amount); | |
vs. | |
PaymentStatus pay(IReservation reservation, IPaymentMethod method, Amount amount); | |
Again Not true. First of all, there is no compelling reason to view "pay" as a | |
verb for Reservation. It is equally plausible to view this as a verb for the IPaymentMethod. | |
So which one is the correct one. At some point, it would start being implemented in multiple | |
classes (IReservation IPaymentMethod and the like) and the actual functionality would be | |
relegated to a "helper" class. So the ReservationManager would resurface suddenly this time | |
masquerading as a helper class. That class would need all the three parameters for it to process | |
anyways! | |
4. The intent may have been to relieve Reservation | |
of responsibility, but this problem of underperformance | |
has spread. Notice that ReservationManager doesn't do anything | |
for *itself* either-it only does things for Reservation. | |
In fact, if Reservation took some responsibility for its | |
own actions, ReservationManager would be out of a job! | |
If we are going to pay an additional complexity tax, | |
what are we getting for it? There are times when the | |
tax is worth paying, such as when the initial class | |
(Reservation) has the opposite problem of doing too | |
much. Classes should have a single responsibility | |
after all. But in this case Reservation has NO | |
responsibilities. Underworked is (at least) as bad | |
as overworked. | |
The intent is not to relieve Reservation of responsibility. Instead, the intent | |
is to separate the data carriers from the strategies since they have different | |
approaches to be applied for managing dependencies between the classes. Dependencies | |
between data carriers are desirable as they materialize a "tree" of objects which | |
mirror data relationships whilst strategies are merely algorithms to be applied on | |
the data. Dependencies between strategies tend to couple applications to | |
implementations and hence are avoidable. | |
The real complexity tax is paid if you have to initiate a data carrier object like | |
Reservation in multiple layers and have to reckon with other classes such as ITransactionGateway | |
which only reside in the service layer and hence should not even be known elsewhere. ITransactionGateway | |
should ideally be in a module that is only "visible" to services and not to anyone else. | |
Also, the situation is hard to mock. I need to think of a mocking strategy for both "data carrier" | |
domain objects and services. | |
A third problem that would manifest itself is that I am creating an "ephemeral" object like | |
Reservation possibly for every request and need to depend on my DI framework to instantiate it since it | |
cannot be instantiated without other dependent classes like ITransactionGateway. This places a | |
direct dependency on the DI framework on a per request level which is not desirable since that violates | |
a dictum that the usage of a DI framework must be transparent to the extent possible. Spring has been | |
known to fail in these situations. I just got off a project where the ATG DI framework called Nucleus | |
posed serious issues when there was this kind of dependency. | |
*/ |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment