High Level Architecture Overview
This document represents the ideal arrangement of components in Confluence. This architecture should be the target of our refactoring, and should inform any new features we add to the system.
For the first three years of its development, little attention was paid to the high-level structure of the Confluence application. As such, it grew organically and developed interesting quirks on the way. This document tries to make sense of Confluence from a high level, to make the application easier to work with, easier to explain and easier to extend.
The goals are:
- Clearly defined separation of concerns
- Clearly defined interfaces between components
- Clearly defined dependencies between components
- Easier integration testing of components
- Looser coupling
Imagine an operating system.
- At the lowest level you have the bootstrap code, which is required for going from a state of having nothing, to one where the rest of the system can be loaded.
- At the next level, the operating system provides device drivers, network abstractions and the like, generic services that any application can use.
- On top of those services you might run an application
The Confluence Bootstrap Process is responsible for bringing up enough of Confluence that the rest of the system can be loaded. In Confluence's case, this involves:
- Locating the confluence home directory and the
- Determining whether the system is set up or not
- Determining if we need to join a cluster or not
- Loading the database connection configuration (either from the config file, or from the cluster)
Based on this information, the bootstrap process can determine what to do next, and provide enough configuration for the core services that they know how to start up.
Bootstrap is implemented as a Spring context, in
bootstrapContext.xml. It is loaded as a parent context to any subsequent Spring context. It is available as a static singleton from
Setup (a digression)
Confluence's in-browser setup requires a number of components that aren't used anywhere else. For example it needs a dummy plugin manager so that i18n works before we have a real plugin manager available. Ideally, setup should be a separate Spring context that is loaded when setup is required, and disposed of when setup is complete.
Currently this is not the case - setup components are loaded as part of the bootstrap context and remain indefinitely. To fix this will need some work on the
atlassian-setup component, which annoyingly conflates setup and bootstrap.
The Main Spring Context
Once the system has been bootstrapped, and setup has (at least) reached the point where we know how to connect to the database, the main spring context is loaded as a child of the bootstrap context. The main Spring context, available as a static singleton from
ContainerManager, contains the remainder of Confluence's Spring configuration, loaded from a lot of different XML files in
The list of XML files to load for the main Spring context is defined in the
contextConfigLocation parameter in
Loading these files in some specific order (as parent/child contexts) might make sense as a way of enforcing component boundaries, but I'm not convinced the benefit is worth the effort.
See also: Spring Usage Guidelines
These are generic services that you might consider useful to any application, like database access, caching, plugins, events, indexing/searching, and so on. A good way to think of the service layer is to imagine a theoretical library called "atlassian-base", consisting only of Confluence's bootstrap and service layers, which could be used as the basis for any new Atlassian web application.
Services can have dependencies on the bootstrap manager, and on each other, but there should never be a circular dependency between services, and there should never be a tightly coupled dependency between a service and application code.
Interdependencies between services should be minimised. When introducing a dependency between services, ask if this dependency is necessary or incidental.
Services are defined in XML config files in
- One file per service
- Each file should have a header comment describing the service, and explicitly declaring any dependencies on other services
- Each file should be divided into "PUBLIC" and "PRIVATE" sections, delineating which components are part of the service's public façade, and which are only for internal use
- All beans defined in services must be explicitly wired. No autowiring.
In the future, once the service system has been bedded down, we might introduce some kind of naming convention for private beans to make it harder to use them accidentally outside the context.
Below the service layer is the Confluence application itself, which is divided into subsystems. More on this when I know what to do with them myself.