Skip to content

Instantly share code, notes, and snippets.

@christianromney
Forked from rajakolluru/MyKindaModel.java
Created August 28, 2012 18:45
Show Gist options
  • Save christianromney/3502072 to your computer and use it in GitHub Desktop.
Save christianromney/3502072 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.
[CR] Nomenclature! "Data carriers" are *not* domain models. They are
DataTransferObjects (DTOs). What I call domain model, you seem to call
Service Object, whereas what I mean by "Service Object" is something
altogether different...so maybe this is part of the reason we're not
aligned.
If you need to do stuff with me you should know to instantiate the correct
<del>service</del> <ins>domain model</ins> 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..
[CR] Again, this boils down to nomenclature. A capital "s" Strategy is an algorithmic interface
we can plug in to solve the same problem different ways, not entirely different problems.
For instance, a LineItemSorter might <<use>> an AscendingPriceSortStrategy or an
AlphabeticalNameSortStrategy to do the sorting internally (or more realistically a
MergeSort or QuickSort strategy).
}
// 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.
[CR] Is this the same Dependency Injection framework we were bashing in the other example?
Here he seems very helpful indeed. ;)
*/
class ReservationManagerImpl implements ReservationManager {
// Dependency injection for the cashier...
public ReservationManager(ITransactionGateway backend) {...}
public PaymentStatus payInFull(IReservation reservation, IPaymentMethod method) {
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.
[CR] Actually you don't. We're still relying on the transaction gateway for that.
But here's a real litmus test...which object knows the answer to
.isWithinFinalPaymentWindow() ?
If there are multiple dudes who know how to transact, then <del>I can orchestrate interactions between
those and provide wrappers around them for horizontal services such as transactions,
security, caching, logging, auditing etc</del> <ins>something is wrong</ins>.
Also, remember the presentation layer's job is not to orchestrate me. Its job is to
populate the model based on the view. [CR] Agreed (along with a few other things).
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.
[CR] We probably need more discussion around this point, with
agreed-upon nomenclature so we can see where we might truly differ.
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.
[CR] Yes, there is—it's called encapsulation and it's a central idea in object-oriented
programming. The data and the methods that operate on that data are co-located in one
place to prevent inappropriate access and mutation of that information by other classes
that really have no business messing with it. *That* is the motivating factor for encapsulation—
to prevent the subtle bugs that emerge when other objects change the state of the data structure
you were relying on.
It is equally plausible to view this as a verb for the IPaymentMethod.
[CR] I don' think so, but I'll concede one could model it differently (though not this way).
If you follow this argument, then you'll note that PaymentMethod would need knowledge of
Reservation even if it's just to know what getters to call–it would <<use>> reservation.
I think that's exactly backwards. Expressing the dependency the other way around is much more natural.
The reservation is the thing you are paying for. In that sense it's a very natural thing to do to it—just
as hold(), and cancel() are. paymentMethod.cancel(reservation) would leave most developers scratching their
heads. reservation.cancelIssuingRefund(customer.paymentMethod()) is almost self-explanatory.
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!
[CR] I'm not a big believer in the "when in doubt, implement it twice on two different objects"
approach. So this argument is specious. You would pick one (the *right* one)-and go with that.
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.
[CR] I agree with 1/2 of this (the object graph part). Most of the operations
performed on business objects are not strategies. Strategies which are generic
algorithms encapsulated within implementations that are *semantically equivalent* but may
differ in interesting ways like time / space utilization. True strategies are like my sorting example:
Selection Sort has O(N^2) running time and Merge Sort has O(N log N) time but they both leave the
data structure in the exact same state when they're done. They are semantically equivalent.
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.
[CR] Not really buying this argument since the ITransactionGateway is never used or seen by clients of
the Reservation class. That detail is handled by the DI container. As for "visibility" any class is
just one import statement away. Someone intent on doing the wrong thing will always find a way.
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.
[CR] This all depends how you model the domain and on whether what you actually have at any given moment
is a reservation. In many cases, perhaps you only have a Quote.
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.
[CR] I'm not sure what this means, but once we accept that dependency injection is a necessary tool
for de-coupling Java code I don't see what the issue is.
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.
[CR] Hard to evaluate this statement. It sounds like Nucleus might have been a problem. I would hesitate to make any
generalizations from that single data point. In any case, I suppose perhaps it's fortunate we're not using Nucleus.
*/
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment