Skip to content

Instantly share code, notes, and snippets.

@rajakolluru
Created August 18, 2012 17:27
Show Gist options
  • Save rajakolluru/3388563 to your computer and use it in GitHub Desktop.
Save rajakolluru/3388563 to your computer and use it in GitHub Desktop.
/*
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