Using a different persistence mechanism¶
The Reahl framework itself contains a few classes that need to be persisted to a database. For example, the WebUserSession is an object that represents the UserSession, and is needed for all the UserSession-related features explained in The web session problem. There is a handful of other classes that work in concert with the WebUserSession.
These persisted classes are written using a specific object persistence technology. The technology used throughout this tutorial is Elixir on top of SqlAlchemy. Rather than tie the Reahl framework to a specific persistence technology, the (small) part of the Reahl framework that is written using Elixir/SqlAlchemy is split into a separate component. This component can be swapped out for a different one which contains an implementation that uses a persistence technology of your choosing.
The Reahl framework itself is agnostic towards the persistence technology used – it needs to be supplied with a few classes, each of which sports a given interface. The framework makes use of a simple form of dependency injection in order to discover the implementation of these classes which it should use for a given application. This section aims to tell you a little more about the role dependencies play in Reahl in general, and how to use dependency injection. It does to at the hand of the example of writing your own implementation of the persisted classes needed by the framework itself, but the mechanism is meant to be used for other means in your application as well.
Right at the beginning of this tutorial, in the Getting started with Reahl, we state that all code in a Reahl program is packaged into some or other component. This component is packaged as a Python Egg. When you write your web application, you write it as such an Egg. As it goes with Eggs, if you want one Egg to use the code in another Egg, the first Egg must declare its dependence on the other one which it is using.
Your web application itself is such a component/Egg, and it uses a number of other components – such as the ones that contain the Reahl web framework itself, or other third party components. In fact, you can visualise your application as a component that sits at the root of a whole dependency graph of components.
Part of the configuration of an application actually specifies what this “root egg” of your project is. You have not seen it mentioned in this tutorial, because it is defaulted to the name of the directory in which your component is developed (that is also what is used as the default for the name of your component). This default works fine for the examples presented while in a development environment. In a production environment, the proper root egg of the web application has to be set explicitly via the reahlsystem.root_egg setting in the reahl.config.py file.
The information gleaned from this dependency graph is very important for the implementation of many aspects of Reahl. Take configuration, for example: each individual component has its own configuration. If component A is dependent on component B, it makes sense to first read the configuration of component B before you read the configuration of component A (which may use B, and thus expect B to be configured!). Similarly, if component A is dependent on component B, the database schema of persisted classes in component A may be dependent on the schema of those in component B. When you create a schema for an entire application, you’d have to first create the schema for the components that are leaf nodes in this dependency tree and work your way towards the root. Database Migrations are also run in a special order: you want to first change the schema of the components at the leaves of this dependency tree, followed by the components that depend on them, etc – until you reach the root, which is most impacted by all these changes. The cleanup phase of migration is run in the reverse order.
All these complications are of course necessary to be able to develop components that do not contain any knowledge of the circumstances of where they will be used. This is the cornerstone (and price) of being able to make reusable components.
As explained at the beginning of this section, the reahl-web component needs a number of classes that are provided by a different component. The Elixir-based implementation of these classes live in the reahl-web-elixirimpl component which in turn is written using the reahl-domain component. Diagramatically this is the scenario:
Normally, reahl-web would merely import the classes it needs from reahl-web-elixirimpl, but that would make reahl-web dependent on reahl-web-elixir without any possibility to swap reahl-web-elixirimpl out for a different implementation. So, instead, the reahl-web component discovers the actual classes it should use during run-time, when an application starts up. The mechanism used to do this is simply the configuration of reahl-web: The configuration for reahl-web takes, amongst other settings:
|web.persisted_exception_class||The class used for PersistedExceptions|
|web.persisted_file_class||The class used for PersistedFiles|
|web.persisted_userinput_class||The class used for UserInput|
|web.session_class||The class used for UserSessions|
The reahl-web component could thus simply read its config file (web.config.py), in which the correct classes to be used are imported and assigned to the settings as listed above. This plan will work, but, it is a bit cumbersome. Besides, do you really want the one writing those config files to deal with such low-level implementation details?
Instead, there’s a bit of a twist to the configuration mechanism to make it usable for this purpose without the need for users to write anything in a configuration file: One component can set parts of the configuration of another. Hence, the reahl-web-elixirimpl component itself modifies the configuration of reahl-web to supply these special settings – without any need for a user to configure anything.
A Configuration class can optionally have a method .do_injections(). This method is called after that configuration has been read. When called, it is passed the entire configuration of the system. The programmer of reahl-web-elixirimpl can thus write code in this method to supply or change the configuration of any component that has been read by this time. To ensure that the configuration of reahl-web is read before that of reahl-web-elixirimpl, reahl-web-elixirimpl is declared to be dependent on reahl-web in its .reahlproject file (an inverted dependency).
Here is the Configuration of the reahl-web-elixirimpl component:
class ElixirImplConfig(Configuration): filename = 'web.elixirimpl.config.py' config_key = 'web.elixirimpl' def do_injections(self, config): config.web.session_class = WebUserSession config.web.persisted_exception_class = PersistedException config.web.persisted_userinput_class = UserInput config.web.persisted_file_class = PersistedFile
(The classes assigned here are defined higher up in the same file.)
One caviat remains: the configuration of each component is checked and verified as soon as it has been read, but before the configuration for the next component is read. If any required setting has not been supplied by this point an error will be raised. Settings that are meant to be injected are still missing by the time they are validated because they are meant to be automatically supplied later, by another component. For this reason such an “injected” ConfigSetting must be declared with the automatic=True keyword argument when created. This serves as a marker for the configuration system to allow them to be set only later on.