Friday, April 25, 2008

Can AOP inform OOP (toward SOA, too? :-) [part 2]

Aspect-oriented programming is still largely code-centric. This is not surprising, as OOP went through the same process: early emphasis was on coding, and it took quite a few years before OOD was ready for prime time. The truth about OOA is that it never really got its share (guess use cases just killed it).

This is not to say that nobody is thinking about the so-called early aspects. A notable work is the Theme Approach (there is also a good book about Theme). Please stay away from the depressing idea that use cases are aspects; as I said a long time ago, it's just too lame.

My personal view on early aspects is quite simple: right now, I mostly look for cross-cutting business rules as candidate aspects. I guess it's quite obvious that the whole "friend gift" concept is a business rule cutting through User and Subscription, and therefore a candidate aspect. Although I'm not saying that all early aspects are cross-cutting business rules (or vice-versa), so far this simple guideline has served me well in a number of cases.

It is interesting to see how early aspects tend to be cross-cutting (that is, they hook into more than one class) but not pervasive. An example of pervasive concern is the ubiquitous logging. Early aspects tend to cut through a few selected classes, and tend to be non-reusable (while a logging aspect can be made highly reusable).
This seems at odd with the idea that "AOP is not for singleton", but I've already expressed my doubt on the validity of this suggestion a long time ago. It seems to me that AOP is still in its infancy when it comes to good principles.

Which brings me to obliviousness. Obliviousness is an interesting concept, but just as it happened with inheritance in the early OOP days, people tend to get carried over.
Remember when white-box inheritance was applied without understanding (for instance) the fragile base class problem?
People may view inheritance as a way to "patch" a base class and change its behaviour in unexpected ways. But truth is, a base class must be designed to be extended, and extension can take place only through well-defined extensions hot-points. It is not a rare occurrence to refactor a base class to make extension safer.

Aspects are not really different. People may view aspects as a way to "patch" existing code and change its behaviour in unexpected ways. But truth is, when you move outside the safe realm of spectators (see my post above for more), your code needs to be designed for interception.
Consider, for instance, the initial idea of patching the User class through aspects, adding a data member, and adding a corresponding data into the database. Can your persistence logic be patched through an aspect? Well, it depends!
Existing AOP languages can't advise any given line: there is a fixed grammar for pointcuts, like method call, data member access, and so on. So if your persistence code was (trivially)

class User
{
void Save()
{
// open a transaction
// do your SQL stuff
// close the transaction
}
}
there would be no way to participate to the transaction from an aspect. You would have to refactor your code, e.g. by moving the SQL part in a separate method, taking the transaction as a parameter. Can we still call this obliviousness? That's highly debatable! I may not know the details of the advice, but I damn sure know I'm being advised, as I refactored my code to be pointcut-friendly.

Is this really different from exposing a CreateSubscription event? Yeah, well, it's more code to write. But in many data-oriented applications, a well-administered dose of events in the CRUD methods can take you a long way toward a more flexible architecture.

A closing remark on the SOA part. SOA is still much of a buzzword, and many good [design] ideas are still in the decontextualized stage, where people are expected to blindly follow some rule without understanding the impact of what they're doing.

In my view, a crucial step toward SOA is modularity. Modularity has to take place at all levels, even (this will sound like an heresy to some) at the database level. Ideally (this is not a constraint to be forced, but a force to be considered) every service will own its own tables. No more huge SQL statements traversing every tidbit in the database.

Therefore, if you consider the "friend gift" as a separate service, it is only natural to avoid tangling the User class, the Subscription class, and the User table with information that just doesn't belong there. In a nutshell, separating a cross-cutting business rule into an aspect-like class will bring you to a more modular architecture, and modularity is one of the keys to true SOA.

4 comments:

Anonymous said...

Dear Carlo,
I'd like to add some random considerations of mine to your as sharp as ever thoughts.
Although AOP is focused on "concerns", I get the feeling that from a pragmatical point of view it is really menat to deal with "features": a word which often translates to "actions" (i.e. methods/functions/verbs).
Thus, we are indeed dealing on some kind of "cross-cutting actions": that is, actions which we wish to build as a kind of "implant" on the previously built general structure.
Here I come to my basic point. Please let point out that it is meant as provocation (that is all I can dare as a not-professional).

Isn't AOP at its core a reinvention of Lisp macros?

If not for its working engines, I think it really is for what concerns its aims and scope: a sort of meta-code which adds to (or modify) existing code, specifically aiming to provide a variation of the original actions.
Of course, my critique depends on my possibly wrong feeling that despite of the variety of tools provided for architectural modification (i.e. variation on the structure of existing classes), in reality what AOP really focuses on is the variation on existing "procedures".
From this point of view, I really wonder whether AOP is going to undermine the very foundation of OOP (the belief that data types are inherently more resilient than teh algorithms applied on them).

Yours
Guido Marongiu
guidomarongiu@yahoo.it

p.s.: I really can't wait about telling somebody: I just got confirmation that I'm going to spend an year in Amsterdam and perform some research there! Wish me good luck!

Unknown said...

Well, at this point it'd be good to discuss more on obliviousness on the c++ side and policy based design. I'm not really a fan of Alexandrescu work mainly because it's applied to low level code aspects (not in sense of AOP :)). But if we consider events as a method to design for interception, maybe a policy it's not so bad as long as we see it as a an aggregate of pointcut (this reminds me of your previous post about delegates and interfaces).

About AOP-Lisp macros: I've never touched lisp, but now I think AOP is something more than a feature of a language. It should be a way to think, after all what does it matter how I implement Aspects? Each language could give me some way to map aspects to a (set of) constructs with diffent degrees of impedance.
But the real thing is that it could add more value to design! And no language feature can substitute rational thinking!
Just my 2 cents.

Carlo Pescio said...

Guido: excellent points :-).

AOP can surely be seen through the low-level, pragmatic perspective of metaprogramming - "simply" altering the behaviour of existing code. Indeed, metaprogramming and metaobject protocols have been extensively explored by the LISP – CLOS communities for years.

There is, however, an alternative perspective on AOP, one which is advocated, for instance, by Kiczales in It's Not Metaprogramming: the perspective of a direct semantics, whereby "we think in aspects".

It is true, however, that in too many cases AOP is still being seen through the low-level perspective: again, this is not surprising: early C++ adopters found useful to think about polymorphism through the low-level lens of a virtual function table.

With your background, I would suggest that you go through Aspect-Oriented Programming:
An Historical Perspective (What’s in a Name?)
by Cristina Videira Lopes (the final text has been published in "Aspect Oriented Software Development", a very nice book by Addison Wesley; the link above is to a draft version). Near the end, Cristina says: "The future of AOP will probably benefit from removing the word "Aspect" out of its name! What’s important for the next generation of programming languages is the exploration of the rich set of referential relations we find in natural languages. That will allows us to appropriately implement pieces of program specification not only as separate chapters, but also as sections, paragraphs and even sentences, in a way that’s much more natural". This is what AOP is really about (and why I said that Knuth's CWEB was the real AOP ancestor). AOP allows to achieve higher modularity by transcending [the tyranny of] a single hierarchy, while still glueing pieces together through powerful referential relations.

With that in mind, to avoid what seems to be a natural clash with OOP, I think we should look more and more at Aspect Orientation as a way to separate and then glue together different concerns. Each concern, however, must be brought to a full-blown [possibly OO] state before it is glued with the others.

Moving from theory to practice: it is one thing to put the full business logic of the "friend gift" rule inside the advice portion of an aspect. This will achieve separation of concerns, but will leave that business rule as a second citizen, grafted upon the existing structure. It is a different thing, however, to create a full-blown FriendGift class, maybe supporting different events (like new user, new subscription, renewal, and so on), or maybe a whole set of classes dealing with marketing and promotions, and using AOP only to glue those abstractions to the "domain" structure. This is probably a better use of AOP whenever we deal with a functional concern; it may be slightly overengineered for some nonfunctional concerns.

Congratulations for your achievement! I'm sure you'll do an excellent job there :-).

Carlo Pescio said...

Fulvio: the biggest difference between raising an event or delegating some behaviour to a policy is the awareness you have of what is going to happen.

More clearly: when the Subscription class raises a generic event like "a new Subscription object has been created", it is not oblivious to the fact that someone might intercept creation. It is, however, totally unaware of what will happen, and as such it does not depend on it.

Policy-based design, in Alexandrescu's words, is reminiscent of the strategy design pattern. You normally extract known behaviour, behaviour upon which you usually depend, into an external strategy, or compile-time policy. It would be quite different, therefore, to know about a Marketing policy, or strategy, and call upon its function in predictable moments: coupling between subsystems would still be there.

Indeed, the biggest failure I usually see in good OO systems is the failure to decouple different subsystems (bad OO systems usually have a coupling problem at a much finer granularity). AOP-like techniques would certainly help there.