#284 Aspect-Oriented Programming

JohnDG Sat 12 Jul 2008

There are few large frameworks developed today that do not make extensive use of aspect-oriented programming. Consequently, it irks me whenever I see a new language without out-of-the-box support for the concept.

I think languages should natively support the ability to query for subclasses of a given class, classes that implement particular interfaces, classes with particular facets having particular values, and so forth. And then to weave aspects into collections of such classes, using POJOs -- or, as they might be called in Fan, POFOs.

I see there was some old discussion about aspects in Fan, but looks like nothing came of it (at least there's nothing in the Fan docs).

Any news on the status of AOP in Fan?

brian Sat 12 Jul 2008

The result of those discussions was the Type Database which maintains an indexed database of all the types of a given installation. This feature isn't finished, but already supports the ability to query types by facets. For example to find the Weblet used to "view" a given object (from FindViewStep):

viewTypes := Type.findByFacet("webView", req.resource.type, true)

tompalmer Sat 12 Jul 2008

I think that APIs for tweaking types at load time should be in sys. And that should be enough for AOP, almost. Is there a "classloader" concept exposed?

brian Sun 13 Jul 2008

Actually I'm not a big fan of bytecode rewriting or tweaking types at load time. I think most problems like that are better solved other ways. I am a fan of generating code at runtime - Fan make this pretty easy. The simplest way is to write some code to a file, and load it via Sys.compile:

fansh> f := `temp.fan`.toFile
temp.fan
fansh> f.out.print("class Foo { Int add(Int a, Int b) {return a+b} }").close
true
fansh> t := Sys.compile(f)
temp_0::Foo
fansh> t.make->add(3,4)
7

More sophisticated runtime emitting can be done Pod.load which loads a pod definition from an InStream (this is pretty close to what Java's class loading allows).

jodastephen Sun 13 Jul 2008

My take is that AOP is mostly a hack around the limitations of existing languages. I would be interested to see specific AOP examples, and to compare how they should be implemented in Fan. Any gaps would then be interesting.

JohnDG Sun 13 Jul 2008

A type database is an essential part of AOP, but it's not sufficient. It's necessary to be able to seamlessly wrap any type with a decorator (perhaps implemented via trap), which can perform logic dictated by the aspect.

AOP is indeed a hack around the limitations of existing languages. It's used to eliminate duplication that cannot be factored out given the syntax of the language (syntax-orthogonal duplication).

For example, take a permissions system. You only want some kinds of users to be able to use some features of your product. The cleanest way to implement this is with annotations or facets:

@secure(permissions="view-source")
Void retrieveFile(File file);

Then weave in an aspect that implements security. Same way for logging and lots of other stuff.

Although not directly relevant for Fan (because threads can't share data), annotations can be used for denoting that some methods should be read locked, and others write locked, and an aspect can be woven in to implement the locking. Nice and clean and gets duplicated junk out of many classes.

brian Mon 14 Jul 2008

I would be interested to see specific AOP examples, and to compare how they should be implemented in Fan. Any gaps would then be interesting.

I agree this is the right approach. Concrete use cases of problems people would like to solve.

It's necessary to be able to seamlessly wrap any type with a decorator

I've kind of always thought we would end up with some kind of decorator feature. It was one of my use cases for the facet design. Having spent any time on it because I haven't run into case where I need it.

JohnDG Mon 14 Jul 2008

Security, logging, and multithreading are but three examples.

Caching is another good use case. Mark methods with some @cache facet to cache the results based on the parameters. Weave in an aspect to do the caching. Works beautifully and eliminates lots of boilerplate code.

RMI or distributed computing is another good use case. Intercepting and redirecting method calls forms the core of any RMI interface. Stubs are hideous boilerplate, and AOP allows a clean solution.

My ideal interface would allow pushing type decorators onto a stack, allowing an infinite amount of decoration. A decorator might be any old object that implements trap. Constructing a new instance of a type would decorate its methods with every decorator on the stack.

Putting something this fundamental into the language (rather than waiting for third-parties to hack it into the language, as they've done with Java) would ensure widespread use and draw more developers to the platform.

yachris Sat 6 Nov 2010

I'm bumping this because I'm curious to know if anything has come of this, and to discuss AOP a bit more.

jodastephen wrote:

My take is that AOP is mostly a hack around the limitations of existing languages.

Well... yeah, but you may be missing the point. That's a bit like saying, "Object Oriented programming is mostly a hack around the limitations of procedural languages."

That may be (vacuously :-) true, but I'm pretty sure we've gotten a bit more out of OOP.

In fact, let me reflect a bit on the mid-80's, when I was starting programming. This was a time when OOP was just starting to get the attention of "main-stream" programming, and it was controversial. The arguments against it (as I recall) came out something like this (Note that I'm not advocating any of these arguments, just reporting what was said :-):

  1. It's needlessly complicated, and really doesn't give you anything that procedural programming doesn't give you (hey, it's all Turing Complete).
  2. It has "imbedded", non-obvious magic. When you create an object, you don't see it, but the Constructor is run! This is confusing, and isn't obvious from looking at the code; there's a function call there, but it's invisible.
  3. Same thing for inherited methods: You look at code and see that it calls some foo function, but it isn't in the code that you're looking at; you have to know where to look for it.
  4. Yeah, there are tools that help mitigate some of this "magic", but they're expensive, and hard to learn and use.
  5. It's difficult, far more than junior programmers can handle, particularly the design of families of interacting objects. So it should only be reserved for the experts on the team; and since it's only good for experts, maybe we shouldn't be using it.
  6. It's not clear how to do design for Objects; and hey, we've been doing Procedural Decomposition for decades, let's not lose that investment.

It's interesting to think about how much we've learned since 1985 about Object Oriented Design; back then we didn't have the Patterns movement, design strategies like GRASP, TDD, BDD, etc.

We now know that there are a lot of great things about encapsulation and dependency management that OOP gives us, that more than pays back the investment in training and tools, and hurray for Open Source, that gave us fantastic free tools!

Fast forward to the mid-2000's, when I start hearing about AspectJ and AOP in general. A lot of similar arguments are being put forth.

What I don't have, now, is an understanding of how to do Aspect Oriented Design.

Let me make another analogy; when Google announced their "Go" language, I thought "Cool, maybe it'll be a good systems-type programming language that can free me from some of the horrors of C/C++!" Then I found out that it doesn't have Exceptions; well, game over man.

Again, from before the dawn of OOP, you had lots of code that looked like:

int err_code;

get_memory_buffers(buf_ptr, size_wanted, &err_code);

if (err_code == 0)
{
  do_processing(buf_ptr, &err_code);
}

if (err_code == 0)
{
  send_output_to(output_file, buf_ptr, &err_code);
}

and so on and so forth (or, alternatively, you had the code return if err_code was not zero).

So in the modern world, we write code more like the following:

get_memory_buffers(buf_ptr, size_wanted);
do_processing(buf_ptr)
send_output_to(output_file, buf_ptr);

and if any problems come up, exceptions are thrown. You put the appropriate catch blocks where they should go (that is, where you can actually do something useful). The code is simpler, more concise, easier to show that it's correct and easier to maintain.

Aspects give you a similar "cleanup". What Aspects give you is, in effect, a _query language_ for your code, and the ability to declare what code should run accordingly.

The query language lets you say things like:

"Wherever method Someclass#foo is called, right before the call happens, run this code".

More interesting, you can do things like "Whenever InternalDbAccess#connectToDb is called in a call-chain that descends from SomeclassTest#testDbFailure, don't do the class but instead raise DatabaseConnectErr".

To make it concrete, I'm working on a program that will run for weeks. I want it to log its progress, and I'm setting up some special classes to re-route the logging output to a socket. But if it's going to run for weeks, I really don't even want the log strings to be generated if nothing is connected to the socket... doing so would add a substantial burden to the garbage collector.

So I could write:

if (SpecialLogger.hasConnections)
{
  log.info("There have been $count runs made in $seconds seconds")
}

but that's starting to look like the old "err_code" hack above. It would be nicer to just write the "log.info" call, but know it won't be executed, due to intelligence added by an aspect.

Oh yeah, and what if I forget to wrap one?

~~~~~~~~~~~~~~~~~~~~~~~~~~~

Wow, wall of text :-)

So, maybe this is more appropriate for thinking about Fantom 2.0 (or 4.0 :-).

rfeldman Sat 6 Nov 2010

Granted, I am an AOP rookie, but it seems like the following would be an easy-to-use solution for the concrete use cases mentioned above: a third Type (after classes and mixins) called aspect.

aspect SearchLogger advises Service, WebMod
{
  ** This would execute after any call to Service.onStart() or WebMod.onStart(),
  ** since both have a slot that matches Void onStart()
  after Void onStart()
  {
    // log that we've started
  }

  ** This would be run before any call to Service.find(),
  ** but would have no effect on WebMod since it has no slot matching this signature
  before static Service? find(Type t, Bool checked := true)
  {
    // log that we're about to start searching
  }

  ** This is a bit more radical and query-ish, but I thought I'd put it out there
  ** for discussion. This would do logging around Service.make(),
  ** WebMod.make(), and WebMod.make(|This|? f) since all match the given pattern.
  around new *ake(*)
  {
    // do some logging
  }
}

go4 Mon 6 Dec 2010

Dynamic Invoke is smart and powerful. But we lost the static syntax check.

Compile static invoke to dynamic invoke, we will get static syntax check and the power of AOP. like this:

class Foo
{
  @Dynamic{ id = "bindable"}
  Void doSomeThing(Str a){..}

  override Obj? trap(Str name, Obj?[]? args)
  {
    Slot slot := this.typeof.slot(name, false)
    if (slot != null)
    {
      if(slot.facet(Dynamic#).id == "bindable")
      {
        log.debug(slot.qname)
      }
      return slot.callOn(this, args)
    }
    return super.trap(name, args)
  }
}

Implicit conversion

Foo().doSomeThing => Foo()->doSomeThing

The @Dynamic suggest compiler to change . to -> .

Login or Signup to reply.