Skip to content
This repository has been archived by the owner on Apr 26, 2019. It is now read-only.
Jakub Janeček edited this page Sep 2, 2013 · 9 revisions

Syringe

Syringe consists of two parts:

  • Syringe dependency injection framework - based on XML/XSD configuration, now obsolete but used by Syringe Perspectives
  • Syringe Perspectives - configuration framework build upon Syringe and Scala traits

The following text describes concepts of Syringe, if you want to dive in directly continue with the tutorial.

Syringe Perspectives

Syringe Perspectives is a configuration framework build on top of Syringe dependency injection framework. The motivation for it came from the fact that XML/XSD configuration may quickly become unmaintainable as the project grows. Perspectives uses Scala programming language since this language has several nice features suitable for modular programming, like traits, lambdas, natural singletons, implicit conversions etc. The name of the framework reminds the basic idea that a developer can look at an assembled application from several perspectives: usually design, decoration and configuration perspectives.

Basic Concepts

As was stated, Perspectives is built on top of Syringe dependency injection. Thus, a developer still creates config beans and uses annotations for marking injectable bean properties. However, instead of generating XSD files when building, the Syringe Maven Plugin generates a Scala source code file - aka palette - containing builders for all config bean components found in the module.

Modules

A module is defined as a Maven project containing config bean classes. The module's POM file contains the Syringe Maven plugin configured so as to generate the Scala file called palette.

Syringe Palette

Palettes

A palette is a Scala trait consisting of builders for all components encountered in the module from which the palette was generated.

Syringe Palette

Component builders classes are implemented as inner classes within the palette trait (e.g. ComponentXBuilder in the picture). For each builder class there is one factory method creating an instance of the component builder. This factory starts with new and is meant to be overridden possibly several times in the hierarchy of subclasses. Each overriding factory method in a subclass contributes to the total configuration of the builder being created.

Important: Every property of a builder can be configured at most once. If more overriding methods attempt to configure the same property, Syringe will complain about it and will fail. Similarly, if a mandatory property is not configured at all, Syringe will fail and navigate you to the problematic property.

In addition to builder factory methods there is, for the sake of convenience, one lazy val (lazily initialized final field) for each component containing a reference to the component builder created by the factory method. This builder instance is suitable in case the component created by the builder is a singleton by nature and it can be used directly in the assembling code. If you need more instances of the component class configured differently you will have to create a new builder factory method calling the default component factory method. Doing it allows separate shared configuration from specific one. The configuration shared by all instances can be placed to the default factory method, while the specific ones can be placed to the new factory method.

Palette MyPalleteA in the following figure declares a new independent builder of ComponentX having fixed property2 (specific configuration) and at same time it fixes property1 to value 0 for all ComponentX instances (shared configuration) by overriding the default factory method.

Extending Palette

If an application uses MyPaletteA it is no longer allowed to configure the fixed properties.

Each palette extends the Module interface implementing the main method making the palette executable as a Scala main class. The default main in the Module trait implementation does nothing so it is up to the palette or the application to implement its logic.

Builders

A builder is responsible for creating an instance of a component class according to its configuration. The configuration is held in the builder's properties exposed as one-parameter methods of the builder. Each property corresponds to a property in the component class (a field marked by @ConfigProperty). The property setters on the builder are designed so as to allow the fluent syntax.

Builder

In very simple applications the properties can be set directly on a builder assigned to a val in the palette. However, in more complex applications assembling should be done by overriding builder factory methods and be separated to more traits, perspectives in other words.

Property Resolvers

An assembling code must set all mandatory properties of used builders (exactly once, not more) or install a so called property resolver (addPropertyResolver) before the build method is invoked.

Value Converters

In case the value assigned to the component instance must be changed, a value converter can be installed to the builder (setValueConverter).

Decorators

The instance created by a builder can be wrapped by another instance or even by more instances, so called decorators. In this case the build method returns the last decorator (the outermost). All decorators must have a property marked by @ConfigProperty(delegate=true) that is meant to hold the wrapped instance and its type must be assignable to the wrapped instance. Since the type of the returning instance differs from the original class (it is the class of the outermost decorator), the caller of the build method should not rely on the Scala's type inference and declare explicitly the expected type of the variable to which the instance is assigned. Assume for example that the builder of ComponentX returns an instance of class ComponentX implementing Runnable. If the builder is decorated, it is the decorator instance what is returned to the caller of build. As decorators should implement the same interface they decorate, it is possible to explicitly declare the variable type as Runnable, as shown in the following snippet:

val x: Runnable = componentX.build
x.run()

Note: This feature is under development at this moment, only component classes that implement some interface should be decorated. The type of the resulting instance should expect the interface instead of the original class.

Perspectives

As an application grows its assemblage and configuration becomes more and more tedious and messy. The key idea of this framework is to facilite the aforementioned tasks by separating them into several phases or perspectives. Each perspective represents one view of a developer on the assembled application. The practice has shown that there are basically three main perspectives present in complex applications: design, decoration and configuration.

By taking the design perspective the developer should recognize overall structure (a graph of interconnected components) of the application or its module. It is the coarsest grained view on the application and it should not be disturbed by finer information like host/port configuration at which the application is listening.

On the other hand, the configuration perspective should contain the finests pieces of the assemblage, which typically is setting configuration options and parameters. Defining it this way, everyone can go directly to this perspective if he needs to tweak some options in the application.

The decoration perspective lies in between the above-mentioned two. Its main purpose is to configure aspect features of the application, like decorating components, logging, security, transactions, applying filters and so on.

The following picture shows an application, whose assemblage is separated to the three perspectives:

Basic Perspectives

Other Scenarios
Two configurations of one application assembled from two palettes

Two Palettes

Two distinct applications assembled from the same palette

Two Apps One Palette

Two configurations of one application derived from two independent perspective stacks

Two App Configs Two Perspectives

Two configurations of one application derived from perspectives extending the base perspective stack

Extending Perspectives

Two configurations of one application derived from two perspectives extending the base perspective stack

Two Extensions Of Perspectives