-
-
Save rajakolluru/3517201 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. | |
[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