Skip to content

Instantly share code, notes, and snippets.

@rajakolluru
Forked from christianromney/MyKindaModel.java
Created August 29, 2012 19:00
Show Gist options
  • Save rajakolluru/3517201 to your computer and use it in GitHub Desktop.
Save rajakolluru/3517201 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.
[RSK] I think we just need to clarify our terminology before we discuss further.
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. ;)
[ RSK] I am not bashing a dependency injection framework. I was one of
the first adapters of Spring and made several changes to their source code initially. But I am saying that it is NOT a good idea to use it in runtime to create domain objects. It is an expert at initializations when it injects the correct strategy. But my contention was that a domain object needs to be instantiated per request not as a singleton. Spring is great at creating singletons not prototypes though they are supported by it. Spring has failed in at least two different instances when the strategies proliferated in its configuration files. That does not mean Spring is bad. it means that it was used inappropriately to create "per request" objects. What we are saying is dont use Spring to instantiate domain objects. use it to inject services.
*/
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>.
[RSK] Difference in nomenclature.
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.
[RSK] agreed.
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.
[RSK] Thanks for the lesson on encapsulation. It might help in trivial situations when the object resides in one layer. This does not scale to Enterprise applications and we can talk about it if you want in a separate discussion.
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.
[RSK] Even if I concede that this argument is contrived, the bigger point I am trying to make is that there are situations when a verb might operate on multiple nouns and it is always not so obvious to associate the verb with one noun only.
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.
[RSK]I dont think we are aligned on the definition of strategies. In my view, a method can either belong to an object or is a strategy that operates on the object. Several strategies can have the same interface. The strategy that you use works on the object. This strategy may differ in different parts of the application. We need to discuss more on this.
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.
[RSK] Not true about the import statement. An import statement is only going to work if the jar file that contains the imported file is in the "compile time" class path. In the CBE application, the presentation layer does not depend on the services layer during "compile time" and hence the import would fail. So we can enforce that developers cannot do it instead of by convention.
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.
[RSK] I think we need to have a bigger discussion on how we intend to use the DI framework and how we implemented code modules. We skimmed over these sections when we talked about the development view point. We can do that but it is hard to do it over the phone.
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.
[RSK] The statement is not theoretical. I had diagnosed issues with Spring when used in this way in two different occasions one in Oracle and another in JP Morgan Chase. You can look at some related musings on this one at http://itmusings.com/architecture/stateful-components-in-ioc
*/
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment