#1337 Database Services Best Practices

rfeldman Mon 29 Nov 2010

I've been trying to figure out the best way to set up a Fantom Service that interacts with a database for a webapp I'm developing. Some things I want to do:

  • When the service starts up, connect to the database and create some prepared statements.
  • Store those prepared statements in memory for quick execution later.
  • Make sure when Wisp shuts down all database connections are closed.
  • Only instantiate one instance of the service and fetch it with Service.find

Some issues I've run into:

  • Since Services must be const, initializing prepared statements in onStart - which seems like the natural place to initialize them - is a pain because by the time onStart runs it's too late to set fields. The best way I came up with to accomplish this was to stash them in a Map that was instantiated on construction, but that's pretty ugly. Maybe it's better to just initialize the database connection and associated statements in make?
  • Since killing Wisp with Ctrl-C does not lead to onStop being fired, what's the preferred way to make sure all Services' onStop methods are fired no matter what before the VM shuts down?

Also, since Service.start is nonstatic, I'm not sure what the preferred way is to start and register a singleton for discovery via Service.find. I tried the traditional singleton way:

const class MyService : Service
{
  private static const MyService singleton := MyService().start

  // ...
}

...though for some reason this did not lead to onStart being fired. Strangely (to me, at least - maybe I'm missing something) if I included the same instantiation line in a class other than MyService, onStart was fired as expected.

vkuzkokov Mon 29 Nov 2010

Maybe it has to do with the fact, that start in your example is called when MyService is loaded. And it won't be loaded until some other code references it.

rfeldman Mon 29 Nov 2010

So if the only references to MyService that I have are the private static singleton defined in MyService plus the following attempt at discovery in a different class...

MyService service := Service.find(MyService#)

...I get an error.

However, if I add a line to the other class explicitly starting an instance of the service, like so...

MyService().start
MyService service := Service.find(MyService#)

...No error, everything works as expected.

Would the first block of code really not load the MyService class (and cause static fields to be initialized) whereas the second would?

Also, assuming that is the problem, what would the preferred method be - if not a private static singleton - to make sure the service is registered for other classes to discover?

vkuzkokov Mon 29 Nov 2010

Even so:

const class MyService : Service
{
  private static const MyService singleton := MyService().start
  static Void load() { }
}

calling

MyService.load
MyService service := Service.find(MyService#)

will do

rfeldman Mon 29 Nov 2010

That will work, absolutely - but I guess I got away from my point, which is that I have the strong sense this is not the way Services are intended to be used in Fantom.

What I'm really interested to know is: what's the preferred way to do this?

Maybe just run MyService().start when the app starts and discover using Service.find from then on?

vkuzkokov Mon 29 Nov 2010

I think it's worth a feature proposal. Every pod should determine an activator class. The simplest approach is to guarantee that activator is loaded right after the pod is loaded. This way we can put pod bootstrapping in activator's static init.

Also this can be done without any changes to Fantom runtime but with special AbstractMain-like class that would get activators for each pod (from props) and call them with respect to dependency.

Anyone with better ideas are welcome.

qualidafial Mon 29 Nov 2010

I've actually been thinking lately that a pod activator would be nice. +1.

brian Mon 29 Nov 2010

Just a note, a type literal will not cause your static initializer to run. Most of Fantom reflection is designed to be used without actually loading the JVM classes.

The proper way to use services is during bootstrap to start all your services, then later use Service.find to discovery them. If you have a more complicated startup scenerio, you can install all of them (which makes them available via find), then call start on all of them. This is what AbstractMain.runServices does.

If you have really complicated startup initialization state to manage, then it is probably best to use an actor. Although if your state is immutable once created then you could use an concurrent::AtomicRef and initialize it onStart.

rfeldman Mon 29 Nov 2010

Interesting, all good thoughts...

What about making sure onStop can run before the VM exits?

brian Mon 29 Nov 2010

What about making sure onStop can run before the VM exits?

I think this will be handled (or enabled for you to do it) via ticket #1314

Login or Signup to reply.