Schlagwörter

, , , ,

deutsche Version

In my projects I have annoyed me about the possibilities of software for different stages (INT, Q, Prod …) to configure. Especially the following things disturbed me:

  • Create different binaries for different stages: the deliverable Software can quickly reach 100 MB or more if using a lot of dependencys. If I have to build and version this software several times for several stages, I have to manage unnecessary complexity and amount of data. I also don’t like the idea to test one binary (eg, the Q-binary) and to set another binary to production (eg the Prod-binary). We can never be sure if that does what it is expected for.
  • The other point is the high redundancy in the various configurations: Configuration of a service can be quite complex when many beans and values ​​(IP address, number of threads, …) are necessary. Often the service for various stages are configured the same, so you quickly have redundant code in your configs.

Now I found a solution based on Spring I feel satisfied with:

The idea is to decompose the Spring config into many individual files, each configures a particular service or a particular module. For each system there may be several different versions of that configurations. The overall configuration is done by a master config that simply includes the existing configurations. The master configs exist one per environment.

I illustrate this by an screenshots from a current project

ScreenHunter_03 Sep. 19 10.58

In the folder „db“ possible configurations for DB accesses are stored. As the implementation of „inMemory“ a HSQL is used. „mysqlLocal“ is a MySQL, running on localhost and „mysqlLive“  is the address of the live DB (with a corresponding read only users). This pattern runs through all components.

The master config uses the offered configurations and merges them as it is required for the concrete system. The Master Config for Local looks something like this:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans" ... >
  <import resource="classpath:/META-INF/spring/db/inMemory.xml"/>
  <import resource="classpath:/META-INF/spring/i18n/default.xml"/>
  <import resource="classpath:/META-INF/spring/mail/smock.xml"/>
  <import resource="classpath:/META-INF/spring/media/local.xml"/>
  <import resource="classpath:/META-INF/spring/template/freemarker.xml"/>
  <import resource="classpath:/META-INF/spring/pdf/iText.xml"/>
  <import resource="classpath:/META-INF/spring/security/none.xml"/>
</beans>

Now we have all configurations for differnet stateges in our application and and eliminated a lot of redundancies in the configuration. Now we just have to inform the application which master configuration should be used.

Spring can process environment variables in the config. We want to make advantage of: The master configs are all called conf{Environment}.xml. Which one should be used now, will decide in a dispatcher.xml, this looks like this:

 <?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans" ... >
  <import resource="classpath:/META-INF/spring/db/conf${environment}.xml"/>
</beans>
 

${environment} will be replaced by the environment environment variable set. If I set the environment variable environment = local on my local development machine, the application starts with with the appropriate configuration. By changing the variable I can also test how the application would behave in the Int configuration (assuming, of course, that I can get from my local computer all servers Int needed).

Deutsche Version

In meinen Projekten habe ich mich immer über die Möglichkeiten Software für unterschiedliche Stages (INT, Q, Prod, …) zu konfigurieren geärgert. Dabei haben mich insbeondere folgende Sachen gestört:

  • Erstellen unterschiedlicher Binarys für unterschiedliche Stages: Die auszuliefernde Software kann mit den richtigen Dependencys schnell an die 100 MB und mehr groß werden. Wenn ich diese Software nun x Mal bauen und versionieren muss, um sie auf die verschiedenen Stages zu bringen, dann habe ich unnötige Komplexität und Datenmengen zu verwalten. Daneben tue ich mir schwer ein Binary zu testen (z.B. das Q-Binary) und daraus zu schließen, dass ein anderes (z.B. das Prod-Binary) das tut, was von ihm erwartet wird.
  • Der andere Punkt ist die hohe Redundanz in den verschiedenen Konfigurationen: Die Konfiguration eines Services kann schon recht komplex sein, wenn dafür viele Beans und Werte (IP-Adresse, Anzahl Threads, …) benöltigt werden. Oftmals werden die Service für verschiedene Stages aber doch gleich konfiguriert und man hat schnell redundanten Code in seiner Konfig.

Nun habe ich eine Lösung auf Basis von Spring gefunden, mit der ich mich gut anfreunden kann:

Die Idee ist es die Spring-Konfig in viele Einzeldateien zu zerlegen, in denen je ein bestimmter Service oder ein bestimmtes Modul konfiguriert wird. Pro System kann es mehrere unterschieliche Konfigurationen geben. Die Gesamtkonfiguration erfolgt durch eine Master-Konfig, die sich einfach der bereits vorhandenen Konfigurationen bedient. Von den Master-Konfigs gibt es so viele, wie es verschiedene Environments gibt.

Anhand eines Screenshots aus einem aktuellen Projekt will ich das verdeutlichen

ScreenHunter_03 Sep. 19 10.58

Im Ordner db sind die möglichen Konfigurationen für die DB-Zugriffe hinterlegt. Hinter inMemory ist eine HSQL hinterlegt, hinter mysqlLocal eine MySQL, die auf den localhost geht und hinter mysqlLive ebene die Adresse der Live-DB (mit einem entsprechenden readOnly-User). Dieses Pattern zieht sich durch alle Komponenten.

Die Master-Config bedient sich der angebotenen Konfigurationen und fügt diese so zusammen, wie es für das entsprechende System benötigt wird. Die Master-Konfig für Local sieht dann etwa wie folgt aus:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans" ... >
  <import resource="classpath:/META-INF/spring/db/inMemory.xml"/>
  <import resource="classpath:/META-INF/spring/i18n/default.xml"/>
  <import resource="classpath:/META-INF/spring/mail/smock.xml"/>
  <import resource="classpath:/META-INF/spring/media/local.xml"/>
  <import resource="classpath:/META-INF/spring/template/freemarker.xml"/>
  <import resource="classpath:/META-INF/spring/pdf/iText.xml"/>
  <import resource="classpath:/META-INF/spring/security/none.xml"/>
</beans>

Jetzt haben wir erreicht, dass wir alle Konfigurationen innerhalb einer Applikation haben und nur noch wenige Redundanzen in der Konfig stehen. Nun müssen wir es nur noch schaffen der Applikation beim Start mitzuteilen welche Konfiguration sie denn verwenden soll.

Spring kann Umgebungsvariablen innerhalb der Konfig verarbeiten. Dies wollen wir uns zu Nutze machen. Die Master-Configs heißen alle conf{Environment}.xml. Welche nun verwendet werden soll, wird in einer dispatcher.xml entscheiden, diese sieht so aus:

 <?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans" ... >
  <import resource="classpath:/META-INF/spring/db/conf${environment}.xml"/>
</beans>
 

Dabei steht wird ${environment} durch die Umgebungsvariable environment ersetzt. Wenn ich auf meinem lokalen Entwicklungrechner die Umgebungsvariable environment=Local setze, startet die Applikation bei mir lokal mit der entsprechenden Konfiguration. Durch Umstellen der Variable kann ich auch testen wie sich die Applikation in der Int-Konfiguration verhalten würde (Vorraussetzung ist natürlich dass ich von meinem lokalen Rechner alle Server erreichen kann, die Int benötigt).