Startup timing issues w/ database initialization and evolutions

This was likely due to a change in Play 2.7 to dev mode in how Play runs web commands (special Play commands like the button for running evolutions). Now dev mode evolutions are handled just before handling the request in the DefaultHttpRequestHandler.

I understand the motivation for this change, which was ultimately to give the user more control and simplify the logic in the Play dev server, but unfortunately it tends to break any apps that initialize database components eagerly on startup, or that have controllers that initialize components on initialization. This problem is made worse by the fact that the DefaultHttpRequestHandler depends on the application’s Router, which in turn depends on your controllers (unless you use @ to inject a provider), so the controllers and their dependencies need to be initialized first.

For what it’s worth, the change is mentioned in the migration guide, but it doesn’t mention that it could cause issues if you’re eagerly initializing database components.

In my opinion it should be possible to work around this by setting the usual play.evolutions.autoApply=true configuration. In that case Play doesn’t need to interact with the user at all, so it should be possible to run eagerly on startup when the ApplicationEvolutions component is initialized. The problem is that the logic in 2.7 has been changed to move even the auto-apply to the HttpRequestHandler, so this doesn’t work with default ApplicationEvolutions component.

Fortunately it’s possible to work around this. In my case I added an override in my application components:

  override lazy val applicationEvolutions: ApplicationEvolutions = new ApplicationEvolutions(
    evolutionsConfig,
    evolutionsReader,
    evolutionsApi,
    dynamicEvolutions,
    dbApi,
    environment.copy(mode = if (environment.mode == Mode.Dev) Mode.Prod else environment.mode),
    webCommands
  )
  applicationEvolutions

You could of course do the same using runtime DI by overriding the binding or replacing the EvolutionsModule. This basically tells ApplicationEvolutions to behave as if we’re in prod mode. In my particular use case this is fine, since I actually find it more convenient to have them auto-run in dev mode, and am comfortable using the configuration to manipulate the evolutions behavior.

I would argue there are two changes that could be made to improve the experience:

  1. Make play.evolutions.autoApply apply the evolutions eagerly on startup, without waiting for the application to load.
  2. Make the Router dependency of the HttpRequestHandler lazy, for example by injecting a => Router or Provider[Router]. This way it’s still possible to use the Play UI for running evolutions if your controllers require the database on initialization, as long as they’re initialized on demand.

I haven’t had as much time lately to contribute to Play, but I’ll start a discussion with the other maintainers to see if these changes make sense to implement.

2 Likes