Posts mit dem Label Java Data Objects werden angezeigt. Alle Posts anzeigen
Posts mit dem Label Java Data Objects werden angezeigt. Alle Posts anzeigen

Freitag, 6. Mai 2016

Tangram Release 1.1

The 1.1 release is a consolidation release removing outdated stuff leaving
room for latest versions of the used libraries and modules and providing some
polishing in the existing feature set.

Beyond the App Engine

The sources and release notes can be found on github.com, the binaries again reside on JCenter. This release includes some notable overdue dependency updates like
  • Servlet 3.1 API now included
  • JSP 2.1 now included
  • DataNucleus updated to 4.1
  • dinistiq updated to 0.5
To allow this, it leaves out the Google App Engine support.

Gretty Plugin

Since not only the Google App Engine has a defect with old releases of Java
Web APIs but also Gradle has a obsolete Jetty plugin with an old version of
Jetty, all examples have been migrated to Gretty which is now the preferred container integration and even application packaging option.

Morphia Support

Latest Tangram applications always used Mongo DB as the storage backend with either JPA or JDO as the API. If you don't need the API abstraction or want an even leaner storage integration (before that DataNucleus JDO and EBean had the smallest footprint) you now can start using the "Mongo DB only Mapping" Morphia.

Markdown Support

Up to now every property using char[] for getters and setters was handled as structured text. It was edited as an HTML fragment (p or div) using the CKEditor.
Starting with this release, every property using org.tangram.content.Markdown for the getters and setters is handled through the CodeMirror editor as Markdown syntax. It is transformed to HTML in rendering.
Also templates can now be in Markdown syntax whenever appropriate while also here the output is transformed to HTML before passing the templates to the other layers of Tangram.

Taking Care of the Modification Date and Time

If you need to track the modification time of an object through the Tangram editor module or the ftp service, you can now do so and the standard storage backed classes of Tangram now do so.

File Restart Cache

The restart caches used for faster restarting of the application with data which should not be changing between sessions of the application now only have to be cleared manually on error conditions but not on changes of the application any more.

Exporting and Importing

The export and import features of Tangram are now strictly symmetrical: Full content export and import  - more or less independent of the storage flavour in use - and a code exporter and importer for the sources stored in the repository.

Fat JARs

Additionally it is now possible to leave out the "war" dependencies since
resources like templates can be placed in the respective JARs.

Extended testing

Tangram now for the first time has a decent test coverage. While there still
is room for more in depth testing of many functions, we now can put more trust
into the single snapshot builds. This was quite important since most Tangram
applications not residing on the Google App Engine already migrated to the 1.1
level.

Sonntag, 22. November 2015

Tangram Release 1.0

Since Tangram reached more than the set of features originally intended back in 2009, it was time to call the current state a 1.0 release.

Why?

The name Tangram points out that the main objective was to create web applications from a fixed set of modules and options which can form nearly any shape you would need. You can start quickly with a limited set of functionality and let the application grow. Fixed set in this case only means that you will need e.g. a model implementation but still have a choice, which one you would want to use. Also the glueing together with a Dependency Injections solution provides several options.
Another intention was to provide reasonable defaults for nearly every aspect where this is possible. You don't need to copy tons of files which do things technically necessary, which you didn't think of at that time. You only set up the things you know that you need them, and all the other features stay quiet. At least they should do something reasonable without getting in your way.

Users

Tangram is in production use since 2011 by Provocon, Ponton, Naturinspiriert.org and others.

Dynamic Extendibility

For many extensions you will not even need a deployment but a change in the data repository forming small and stable cores where the application as whole can be modified to changing requirements quickly, easily and securely.
Apart from the obvious presentation stuff this includes Groovy codes to create and parse URL, extend the business logic, and even the model layer with new model classes.

Core Features

We provide object oriented templating for objects from JPA, JDO, EBean, and CoreMedia repositories. The core system emphasises dynamic web programming through CSS, JavaScript, Velocity Templates, and Groovy Codes in the repository together with basic CDN support, code minification, and caching support.

URLs can be formatted in nice, SEO friendly schemes and be easily mapped to actions to provide the result views for users.
The implementation of authentication and authorization now uses the pac4j set of libraries to provide a seamless and easy integration of plain user lists in files or the repository, OAuth providers, OpenID providers, Google App Engine user service and others for the web application and the base system itself.
This authentication solution is used grant access to the system itself but also to provide support for protected content areas in your applications. You only have to focus on the question, which content needs to be protected by a access granting scheme. Logins can be a generic login page or integrated in you application design.

Generic Editor

Except for CoreMedia we provide a generic web editor which is now responsive (there is now separate mobile editor anymore) which can be loaded from the respective cloud locations of the used components (CKEditor, CodeMirror). Also it can be extended with our dynamic web programming facilities.

Glueing Stuff Together

The mentioned default set of configurations and the option to customize this to your needs is achieved by Dependency Injection libraries. Tangram supports the usage of the Spring Framework, Google Guice, and dinistiq. It should be possible to add other solutions as well provided that they offer a decent set of functionality (which is not always the case for smaller DI libs.) including e.g. optional values, overriding of configurations, and deep generics introspection.

Readware

To illustrate the usage and provide a nice starting point, a set of example applications is provided in sync with the releases.
A documentation wiki is now starting.

Where the 1.0 Release Ends

One real limitation is the older set of JSP/Servlet APIs which need to stay in place to support the Google App Engine. So this is the final release to support the App Engine to be able to move ahead to newer APIs for several areas. This will not be achieved in the 1.0 branch.

Mittwoch, 28. Oktober 2015

Gradle Plugin for EBean, JPA, and JDO Enhancing along with Minification and Overlaying

During the development of the Tangram framework project a set of build related things went into a plugin for the Gradle tool.
The functionality of this Tangram Gradle Plugin is only in very small parts directly related to Tangram. It is more or less a general purpose plugin for applications needing
  • Byte-code transformation of model classes for
    JDO, JPA, and Ebean ORM layers
  • Minification of CSS and JavaScript codes to be placed in WAR artifacts
  • Support underlying of WAR files into others (similar to overlays)
The good news for today is, that with the latest version 1.0.5 the plugin can be used from the central Gradle plugins repository with the - not very surprising - id tangram.gradle.plugin. So some of the usage notes have to be aligned with this situation.

Usage

Just a few lines have to be added to your Gradle build script to use the plugin:

plugins {
  id "tangram.gradle.plugin" version "1.0.5"
}

All of the following steps described here take place without any additional configuration.

Prepare EBean, JPA, and JDO Model Classes

When used with Java projects - and when some data model classes are discovered, - the plugin tries to prepare them for use the respective Object Relational Mapper (ORM). The ORM APIs supported are
These APIs in turn are supported by a number of implementations. The supported implementations are
These OR-Mapper API implementations require (DataNucleus and EBean) or recommend (the others) to apply byte-code transformations called "Enhancing" or "Weaving" to the class files. The compiled code is extended with some database access support to implement the active record pattern more or less seamless.
The API and implementation library in use is discovered from the names of the elements of the class path of the project. If one of the mentioned libraries is found, the corresponding byte-code transformation is applied to the appropriate step (post compile or pre jar creation).

martin@nelson:~/proj/tangram/sites/naturinspiriert$ gradle clean build
:clean
:compileJava
Performing DataNucleus JPA byte code transformation.
ENHANCED (Persistable) : org.naturinspiriert.RootGroup
ENHANCED (Persistable) : org.naturinspiriert.Topic
ENHANCED (Persistable) : org.naturinspiriert.AbstractGroup
ENHANCED (Persistable) : org.naturinspiriert.ImageData
ENHANCED (Persistable) : org.naturinspiriert.Article
ENHANCED (Persistable) : org.naturinspiriert.Linkable
ENHANCED (Persistable) : org.naturinspiriert.Group
DataNucleus Enhancer completed with success for 7 classes. Timings : input=60 ms, enhance=54 ms, total=114 ms. Consult the log for full details
7 classes enhanced.
:processResources
:classes
:jar
:war

:assemble
:compileTestJava UP-TO-DATE
:processTestResources UP-TO-DATE
:testClasses UP-TO-DATE
:test UP-TO-DATE
:check UP-TO-DATE
:build

BUILD SUCCESSFUL

Total time: 4.53 secs

(Highlighted message to indicate use of the DataNucleus Enhancer)

The byte-code transformations directly use the transformer of the respective library in use except for the OpenJPA case, where the ant task of the enhancer is integrated. Some of the transformers issue some logging.

Switching Off

Additionally it is possible to switch of the byte code transformation for JPA annotated classes by adding to your build file

// build.gradle: 
enhancer.enabled=false

in case this might be necessary e.g. to only use the other parts of the plugin. Also EclipseLink, Hibernate, and OpenJPA support the use of plain Java classes without the byte-code transformation.

JavaScript and CSS Minification

When used in conjunction with the war plugin, CSS and JavaScript resources are automatically minified.
The plugin checks for resources with a filename extension .css for Cascading Style Sheets and .js for JavaScript. Matching resources are minified using the YUI Compressor.
It is not possible to minify resource included from archive files but only file resources local to your project. So contents from archives - while being included in the resulting web archive - cannot be minified. (We would expect WAR files to contain minified resources like WAR files generated using this plugin do.)

Web Application Underlying

The plugin introduces a configuration named webapp for modules using the war plugin. Dependencies given for this configuration are extracted into the resulting war artifact.

// build.gradle:
dependencies {
  webapp "tangram:tangram-core:$tangram_version:war@war"
  webapp "tangram:tangram-editor:$tangram_version:war@war"


  compile "tangram:tangram-core:$tangram_version"
  // Persistence API JPA
  compile "tangram:tangram-jpa:$tangram_version:nucleus"
  compile "org.datanucleus:datanucleus-api-jpa:$versions.datanucleus"
  compile "org.datanucleus:datanucleus-core:$versions.datanucleus"
  compile "$versions.jdo_api"
  compile "$versions.persistence_api"
  runtime "org.datanucleus:datanucleus-mongodb:$versions.datanucleus"

  compile "tangram:tangram-editor:$tangram_version"
  runtime "tangram:tangram-dinistiq:$tangram_version"
  runtime "org.slf4j:slf4j-log4j12:$versions.slf4j"


  providedCompile "$versions.servlet_api"
  providedCompile "$versions.jsp_api"
}


This process is not really described well if called overlay so I call in underlying, since your web application's directory in fact is the overlay so the other archives referenced and included must be an underlying.
If your WAR relies on the contents of another pre-packaged or incomplete WAR, the contents of the latter will be copied into your resulting web application while you can override any file in this archive from your local web application contents directory.

Version List

The plugin introduces a version object which collects some version strings for a number of libraries. This ensures that any project using the plugin can use these libraries with recent versions and version changes are applied in sync. The use of this part is optional and you have to explicitly use the versions in your build file since this cannot be applied transparently.
Some random examples:

dependencies {
  compile "org.pac4j:pac4j-openid:$versions.pac4j"

  compile ("org.apache.openjpa:openjpa:$versions.openjpa") {
    exclude group: 'asm'
  }

  compile "org.eclipse.persistence:org.eclipse.persistence.jpa:$versions.eclipselink"
  compile "$versions.persistence_api"

  compile "org.hibernate:hibernate-core:$versions.hibernate"
   
  compile "org.datanucleus:datanucleus-api-jpa:$versions.datanucleus"
  compile "org.dataucleus:datanucleus-core:$versions.datanucleus"
  compile "$versions.jdo_api"
  runtime "org.slf4j:slf4j-log4j12:$versions.slf4j"

  testCompile "org.testng:testng:$versions.testng"

  // your container will have this for you
  providedCompile "$versions.servlet_api"
  providedCompile "$versions.jsp_api"
}


Manual Mode

Of course it is still possible to call the methods performing the different tasks directly like described in the 0.9 plugin blog post. This should only be necessary if you e.g. want to enhance files in the unit test section of your code, which is considered a very rare case.

Samstag, 3. Oktober 2015

Gradle Plugin for JDO, JPA, Ebean etc in Tangram grown up

As part of the Tangram framework project a set of build related things went into a plugin for the Gradle build tool.
Only in part this plugin is directly related to Tangram but offers some generic tools suitable for many projects.
The plugin does
  • Support underlying of WAR files into others (similar to overlays)
  • Minify CSS and JavaScript codes to be placed in WAR artifacts
  • Byte-code transform model classes for JDO, JPA, and Ebean ORM layers
Up to version 0.9 the plugin was more or less a collection of utility methods. It is now redesigned to do the same job transparently when appropriate and at the appropriate stage of the build process.

Usage

Just a few lines have to be added to your Gradle build script to use these add on functionality:

buildscript {
  repositories {
    jcenter()
  }
  dependencies {
    classpath 'tangram:gradle-plugin:1.0.0'
  }
}


apply plugin: 'tangram'

All of the following steps described here take place without any additional configuration.

Web application underlying

The plugin introduces a configuration named webapp for modules using the war plugin. Dependencies given for this configuration are extracted into the resulting war artifact.
This process is not really described well if called overlay so I call in underlying, since your web application's directory in fact is the overlay so the other archives referenced and included must be an underlying.
If your WAR relies on the contents of another pre-packaged or incomplete WAR, the contents of the latter will be copied into your resulting web application while you can override any file in this archive from your local web application contents directory.

Minification

When - again - used in a project also using the war plugin, all files included from the project directory will be checked for CSS and JavaScript file extensions. Matching files are minified using the YUI Compressor.
It is not possible to minify resource included from archive files. So contents from archives while being included in the resulting WAR cannot be minified. (We would expect WAR files to contain minified resources like WAR files generated using this plugin do.)

Handle Model Classes

When used with Java projects and when some data model classes are discovered, the plugin tries to prepare them for use with one of the ORM frameworks Java Data Objects (JDO), Java Persistence API (JPA), or Ebean.
The OR-Mappers require or recommend to apply byte-code transformations called "Enhancing" or "Weaving" to the class files for the data model related classes.
The plugin supports DataNucleus Access Platform (JDO and JPA), EBean, EclipseLink, Hibernate, and OpenJPA. All the calling options from the 0.9 version of the plugin have be removed and the APIs are auto-discovered so the whole process is now - except for logging output - transparent during build.

Version List

The plugin introduces a version object which collects some version strings for a number of libraries. This ensures that any project using the plugin can use these libraries with recent versions and version changes are applied in sync. The use of this part is optional and you have to explicitly use the versions in your build file since this cannot be applied transparently.
Some random examples:
dependencies {
  compile "org.pac4j:pac4j-openid:$versions.pac4j"

  compile ("org.apache.openjpa:openjpa:$versions.openjpa") {
    exclude group: 'asm'
  }

  compile "org.eclipse.persistence:org.eclipse.persistence.jpa:$versions.eclipselink"
  compile "$versions.persistence_api"

  compile "org.hibernate:hibernate-core:$versions.hibernate"
   
  compile "org.datanucleus:datanucleus-api-jpa:$versions.datanucleus"
  compile "org.dataucleus:datanucleus-core:$versions.datanucleus"
  compile "$versions.jdo_api"
  runtime "org.slf4j:slf4j-log4j12:$versions.slf4j"

  testCompile "junit:junit:$versions.junit"

  // your container will have this for you
  providedCompile "$versions.servlet_api"
  providedCompile "$versions.jsp_api"
}

Manual Mode

Of course it is still possible to call the methods performing the different tasks directly like described in the 0.9 plugin blog post.
Also it is possible to switch of the byte code transformation for JPA annotated classes by adding to your build file

// build.gradle: 
enhancer.enabled=false

in case this might be necessary e.g. to only use the other parts of the plugin.

Sonntag, 17. August 2014

Tangram Release 0.9

Over the last year, Tangram has changed very much and does not look the same in all of its modules and options.

Persistence Options

The Tangram Dynamic Web Application Framework has been extended to support the Java Persistence API (JPA) and EBean as persistence layers in addition to the already available Java Data Objects solution. This greatly extends the number of options for platforms to use with Tangram.
The Tangram examples reflect this with the JDO/DataNucleus, JDO/Google App Engine, EBean, and JPA example where the later is switchable with tested API implementations for OpenJPA, EclipseLink, DataNucleus, and Hibernate.
The simple and more or less generic JDO editor now is a generic editor for all the storage solutions and built as a separate module. Except for Google App Engine this module can be left out if there is another solution available to get the content into the repository.

Dynamic Model Extenions

In addition to the Groovy implemented classes stored in the repository, which are used to extend the view and controller part of the application, you can now create JDO annotated classes which are immetiately usable as model classes in the repository. This option can only be presented for the JDO implementation.
To make the building of Tangram itself, any Tangram application, or even any application using byte-code transformation for JDO, JPA, and Ebean easier, a gradle plugin has been introduced. The examples and over blog entries illustrate the usage of this plugin.

IDE Integration

To be able to synchronize codes stored in the repository a separate FTP module has be introduced. Most IDE due to their strong PHP past support FTP better than any other protocol. CSS, JavaScript, Groovy codes can be imported and exported.

Generic Import and Export

Additionally and apart from the FTP module Tangram now contains a generic importer and exporter for the whole content stored in the repository. The XML representation can be used to transfer content between application using JPA, JDO, or EBean. IDs cannot be preserved.

Framework Independency

For the whole Tangram code the Springframework now is only an option to do the setup of the application by Spring's Dependency Injection IoC-Container and for the controller and view parts of the application.
Along this way it was easier to present a custom DI component, Dinistiq, than to use one of the available solutions. Tests have been done with Google Guice, TinyDI, and JSR-330. As of Tangram 0.9 none of these are more than experiments. Work on a Tangram JavaEE integration is also in an experimental stage and would require a major refactoring of some parts of Tangram, see other entries in this blog on the JavaEE topic.
It should now be fairly easy to do more experiments with other IoC/DI frameworks and containers or to integrate other web frameworks starting from the new plain servlet based solution.

Options

Tangram 0.9 has been tested, verified, or is even in production use on the following platforms: Google App Engine, run@CloudBees, OpenShift, and Standalone with Apache Tomcat using RDBMS or MongoDB.
The option matrix now looks like this:
Persistence: JDO, JPA, EBean
IoC: Springframework, Dinistiq
Security: Spring Security, Apache Shiro
Hosting: On Premise, OpenShift, Cloudbees, Google App Engine
Storage: RDBMS, No-SQL e.g. MongoDB, Files

Code Reduction

Tangram started as an idea to plug existing components together to form a web application with as little glue code as possible. Some of the ideas like object oriented templating though had to be coded explicitly. After this starting point the Tangram codes started growing and the 0.9 release got an additional design goal to stop this. Still some of the codes in Tangram duplicate functionality which would otherwise externally be available but avoids a depedency just to call one method. And there are still codes duplicates within Tangram where the different options e.g. for persistence might get a stronger common code base. In these areas it is still not clear if they will differentiate or grow together.

Outlook

The experiences with CapeDwarf showed the way into a Java EE module as an alternative to dinistiq, the Springframework, and the many tests with e.g. Google Guice. The not that encouraging experiences with the EBean ORM might lead to discontinued support for that module. Dinistiq will become more Java EE / CDI compatible and CDI without the full Java EE stack also seems to be an interesting option. Work on the generic import and export functions using XStream needs to be extended and easier integration of OpenID or OAuth outside of the Google App Engine is needed for many applications. Google App Engine with JPA is still on the list but not desperately needed.

Donnerstag, 17. Juli 2014

Not that Groovy

Ist es eigentlich schon allgemein akzeptiert, daß Web Anwendungen nicht mehr davon ausgehen dürfen, daß es für sie eine entspannte Startphase gibt? Daß sie auch beim ersten Request für den Endbenutzer ausreichend schnell antworten sollten? Es klingt immer noch häufig so, als wären "teure" Dinge, die nur einmal in der Anwendung passieren kein so großes Problem und das man dieses "teure" einfach auf die Startphase verschiebt.
Alles was ich hier schreibe, ist nur für Projekte relevant, in denen ein Deployment nennenswerte Kosten verursacht oder es aus anderen Gründen keine gute Idee ist, ständig die gesamte Software installieren. Mir war diese Stabilität mit großen Systemen auf Basis CoreMedia immer ein so großer Vorteil, daß ich das auch mit Tangram im kleinen nachgezeichnet habe (bzw. dort sogar für die großen Projekte vorgezeichnet habe). Dennoch muß man auf wechselnde Anforderungen genauso dynamisch reagieren, wie auf die Anfragen der Kunden an das System.

Messungen mit Groovy-Code in der Datenbank

Um die Software einfacher und schneller an Anforderungen anzupassen, habe ich mich ja vor Jahren auf Groovy-Code in einem irgendwie gearteten Repository festgelegt. Wir kennen das von Stylesheets, JavaScripts, Templates (z.B. Freemarker, Apache Velocity).
Damals habe ich für einen großen deutschen Telekommunikationskonzern eine Reihe von Messungen durchgeführt, die deutlich belegt haben, daß der Zugriff auf eine fertig kompilierte Groovy-Klasse - oder gar eine vorher abgelegte Instanz davon - keine Nachteile gegenüber dem Java-Code des statischen Programmteiles hat.
Für den einzelnen Request einer Webanwendung ist es also vollkommen egal, ob der Code nun deployed wurde oder in der Datenbank liegt.
In der Folge hat die typische Tangram Webanwendung kaum noch Anteile an Java-Code (siehe z.B. amor oder dragon-map usw). Es wird alles über Templates mit Apache Velocity für die Darstellungsschicht und Groovy-Code für die Geschäftslogik oberhalb der Datespeicherung bis hin zur URL-Struktur erledigt. (Mit JDO kann sogar ein Teil des Datenmodells so in den dynamisch veränderbaren Teil verlagert werden.)
Die Effizienz bei der Ausführung wird durch einfaches Kompilieren und Instanziieren beim Speichern der Codes erreicht.
Und hier genau schlummert ein Problem, das sich mir nun richtig stellt, wo einiges an "dynamischen" Codes (also solchen in der Datenbank, die potenziell häufiger geändert werden) zusammengekommen ist:
Im Gegensatz zu den Aufrufen der Anwendung und dem direkten Benutzen der Instanzen erhalten wir beim Start des Systems einen deutlichen Performance-Nachteil, da hier nun einiges an Datenbankabfragen und Kompiliervorgängen sowie Instanziierungen zusammenkommt. In der Summe ist das deutlich aufwendiger als die entsprechenden Vorgänge mit statischem Java-Code.
Warum interessiert der Start aber nun gerade wieder? In den meisten Szenarien kommt so etwas doch nicht gerade häufig vor.

Willkommen in der Wolke

Für einen selbstbetreuten "on premise" Servlet/JSP Container mag das stimmen, aber gerade für die "billigen" Modelle in der Wolke und z.B. auch die Google App Engine, OpenShift oder run@cloudbees stimmt das überhaupt nicht, da hier zum Skalieren und bei Nichtbenutzung der Trick gerade darin besteht, Instanzen der Anwendungen herunterzufahren oder neue hinzuzustellen.
Bei Instanzen auf der Google App Engine, bei denen keine sonstigen Zusicherungen eingestellt sind, führt das zu einem netten Hinauf- und Herunterfahren wie ein Jojo. Bis Version 0.7 beinhalten alle Tangram Anwendungen einen Cron-Job, der genau das verhindern sollte, indem die Anwendung sich selbst aufgerufen hat. Vor einiger Zeit habe ich mich dem Problem nun endlich gestellt und versucht, die Startup-Sequenz einmal zu tunen. In der Ausgangslage sind wir bei einem Erst-Start im Bereich von gerne 30s abgelaufene Zeit.

Spring Tuning

Das ist nicht das erste Mal: Bereits früher hat das Springframework gezeigt, daß es den Komfort für den Entwickler mit "Kosten" beim Start belegt. Der Scan der Klassen nach Annotationen und Komponenten sowie deren vollautomatisches Zusammensetzen dauert auf einem mittelprächtigen Rechner zwei bis drei Sekunden, in der App-Engine jedoch gerne bis zu acht Sekunden, die ein Kunde am Browser warten müßte. Das konnte damals durch das Eingrenzen beim Scan reduziert werden:

    <context:component-scan base-package="org.tangram" />

Die Angabe des Base-Package ist hier wichtig und grenzt den Bereich der Klassen, die geprüft werden, nachhaltig ein, was einige Sekunden bringt. In Zukünftigen Versionen sollten hier noch weitere Einschränkungen greifen, weil das "base package" org.tangram immer größer wird und über eine Anzahl von Jars verteilt ist. Ab Version 0.8 müssen daher all Komponenten, die gerne gescannt werden möchten "org.tangram.components" mit Vornamen - also Package-Namen - heißen.

    <context:component-scan base-package="org.tangram.components" />

Dazu wurden einige Klassen in den Paketen verschoben und es bringt wieder einmal ein paar Bruchteile von Sekunden ein. Lohnend, aber es durchbricht die fachlich Sortierung der Klassen ein wenig. (Aus org.tangram.jdo wird org.tangram.components.jdo - man findet sich also schon zurecht)

Caching

Moment! Wieso Caching? Das Füllen der Caches - soweit zu dem Zeitpunkt hilfreich oder notwendig  - ist doch gerade das, was den Start u.a. so langsam macht. Aber die Inhalte der Caches werden dann bei Start genau dieselben sein, wie beim letzten Herunterfahren - oder dieselben, die schon eine andere Instanz berechnet hat. Es wäre also ganz nett, wenn man einfach auf diese Wert zurückgreifen könnte.
Das tut Tangram letztlich auch bereits seit langer Zeit in der Google App Engine: Der Scan nach Klassen, die zur Persistenz-Schicht gehören, fällt in die gleiche Rubrik wie der Scan des Spring-Framework und die Daten, die sich die BeanFactory hier merken muß, werden in der Google App Engine im Memory Cache über die JSR107 Schnittstelle gespeichert.
Diese Schnittstelle soll uns nun helfen, noch mehr Vorgänge nur so häufig auszuführen, wie es notwendig ist, unabhängig davon, ob es wir von einem Start-Request sprechen oder mitten in der Arbeit sind. Das macht die Webanwendungen Wolken-tauglicher als sie es bisher sind.
Die Benutzung dieses Caches ist denkbar einfach:

try {
  CacheFactory cacheFactory = CacheManager.getInstance().getCacheFactory();
  jsrCache = cacheFactory.createCache(Collections.emptyMap());
} catch (CacheException ce) {
  log.error("()", ce);
} // try

Und danach ist es mit put() und get() getan, wobei man natürlich immer gewahr sein muß, daß einmal nichts im Cache ist. - Und einige kompliziertere Sache - obwohl java.lang.Serializable markiert - kann man zwar hineinstopfen, findet sie in der Admin-Oberfläche, bekommt sie aber nicht mehr heraus.
Das gilt leider insbesondere für per JDO persistierbare Objekte und kompilierte Java Klassen.

Tunen wo es wehtut

Langsam ist bei einem leeren Cache das beziehen aller Codes, die für die Website notwendig sind, aus dem Datastore. Hier geht es schon bei wenigen Codes eher um Sekunden als die oben zitierten Bruchteile. Also enthält Tangram ab Version 0.8 einen Simplen, persistenten Query-Cache, der genau wie der transiente Cache die IDs der Objekte der Ergebnislisten den Queries erfaßt und ablegt. Dieser Cache bringt der gesamten Anwendung bei ersten Aufrufen einer Instanz eine deutlichen Schub bei abfragebasierten Anwendungen.
Mit diesen ID-Listen muß man sich leider immer noch an den Datastore wenden, um die Objekte zu beziehen, da diese Objekte ja nicht in den Cache gelegt werden konnten.
Diesen Schritt aber wiederum kann man sich bei den Codes gut sparen, in dem man sie beim Lesen in reine transiente Objekte umwandelt (hey, es sind Codes!) und sie dann im persistenten Startup Cache ablegegen zu können.
Jetzt ist eine der längsten Phasen beim Starten das Kompilieren der Groovy-Klassen. Hier den Binärcode zu cachen hat sich bisher als nicht umsetzbarer Ansatz erwiesen. Aber evtl. kommt das ja auch noch. So ist der limitierende Faktor beim Startup auf die Anzahl der Groovy-Codes und den Compiler gesetzt. Mehr ging bisher nicht, aber es hat den Startup in der Zeit gedrittelt. - Und die Ansätze daraus sind verallgemeinerbar für Anwendungen in der Wolke.

Sonntag, 13. Juli 2014

Byte-Code Transformation is no big deal

To avoid another language in my web projects I'm using Java as the design language for the Objects to be persisted as well. It seemed easy to use with the different ORM Standards and implementations available.

Take me from Java to the Database

Many ORM implementors tend to recommend using a byte-code transformation process to make the classes usable in their respective persisting contextes (e.g. http://www.avaje.org/doc/ebean-userguide.pdf - Chapter 15). This in fact means, after you did your job of coding and compiling the classes, some other component takes this code and transforms it into some other code additionally dealing with the ORM/Database related stuff.
The idea is, to avoid runtime penalties or the generation of subclasses dealing with the additional database related issues which would show up at runtime potentially screwing up your idea of the class hierarchy. (Which it did for me. See below.)

Why class weaving or enhancing is a big deal

Of course this still means that you are running code, you don't now in detail.
The assumption of any of the ORM framework authors is, that the byte-code transformation process can be easily automated and as far as possible be hidden from the application developer. JPA based JEE applications are expected to do the transformation at deployment time to the container - so this doesn't even happen within your development tool-set.
My experience is a different story. And the hiding of things during development once again was no good idea for me. 

The easy Start

I started with the Eclipse IDE some years ago and the Google App Engine Plugin. It does the byte-code transformation for the JDO implementation from DataNucleus automatically at compile time. This worked fine as long as I was coding in "play-around" mode. When the code started to grow into modules, from time to time the classes were propagated to the client module unenhanced. This is were I learned what the use of DataNucleus feels like, when in fact just the transformation is missing (of course it doesn't tell "you missed to transform classe this-and-that"). I got around these issues with the dumb "clean nearly everything in your work environment" pattern.

Build System Integration

Things got even more complicated when I started to write build scripts, since the project grew and was supposed to be published. You don't want to give anyone a 20 page description on how to setup the IDE just as you did. You simply give friends a script which describes the necessary parts in human and machine readable form. So the project gets cloned from the source and a simple build tool call - hopefully in default mode with no to few options -  will create a usable result.
But the promise to support me as a developer from the ORM provider still holds true for these situations. I was just expected to change the way I was using the transformation tool. DataNucleus comes with an ant task, a compiler plugin and so on. Since I didn't want to use the obsolete legacy tools Maven or ANT (hey, why not use make or punch cards?) I "simply" plugged in the compiler plugin since there is no direct support for Gradle and the integration of the ANT task was not that easy at the initial try.
First of all this compiler plugin was not able deal with all of the versions of the Oracle Java Compiler and all language levels beyond "Java 6" so I had to prepare the source code carefully.
This gave me enhanced classes and sure the enhancer was running, but...
When packaging the classes to JARs as build systems tend to do after compilation nearly automatically, those classes where unusable again with the error messages I was already familiar with.

Unit-Testing the Byte-Code Transformation instead of my Code

At this point in time I started writing JUnit tests to test, if my build environment was working and not to test if my code was correct. This gave me the impression that some things are going wrong.
I learned, that the compiler plugin took some time after compilation before it started to "enhance" (byte-code transform) the class files. It used some sort of threading for this so that Gradle already had packaged the jar files, before the process was completed. I started to add some 10s waiting to my build scripts. Argh...

Refactoring - Get the same Thing you already had

I took a second look at the DataNucleus Enhancer's ANT task to integrate this into the build process as a Gradle task without those eratic 10s of waiting. I also needed this step since I was updating DataNucleus from the old version used in the Google App Engine at that time to a newer one also meant for stand-alone use.

Use other APIs as well like they were simple Libraries

After all these pieces were working, I started playing around with the Java Persistence API JPA. Also the implementations of JPA I came accross - OpenJPA, EclipseLink, and again DataNucleus - recommended the use of byte-code transformations called Enhancement (OpenJPA and DataNucleus) or Weaving (EclipseLink).
The integration of that many APIs and byte-code transformers made things more complicated again, while the code I wrote still is not that complicated. It's just the byte-code transformation which adds to the complexity. I needed to present OpenJPA, EclipseLink, and DataNuceus Versions of my single JAR archive with only very few classes and only two of them needed to be byte-code transformed. Additionally with JPA I have the option to use the original classes without byte-code transformation in some scenarios with certain limitations (Only DataNucleus is really capable of a automatic discovery of available classes for database access, the others need detailed lists passed over to the implementation in different ways. This is anything but portable!)

Stop pretending it is easy and write a decent Tool to do the Job

Since not all of the implementations can be on the compile time classpath of the JPA relying portions of my project, it was now time - just because of the necessary byte-code transformations - to write a Gradle Plugin dealing with this.
Very easily this plugin was generic enough to be used in any project using JPA, JDO, or Ebean as the ORM Solution for Java and the Gradle build tool.
Two third of the work on the build-scripts of the Tangram dynamic webapplication framework were related to the byte-code transformations over the last five years.

Conclusion after some Years

So my best friend now is OpenJPA which can relatively easy be used without transformation. Yes, it presented me the nice subclassing issue where I am at runtime dealing with subclasses of the classes I designed myself, but this was solvable with half a dozen lines of code.
My second best friend is DataNucleus where I am now able to integrate the byte-code transformer into the runtime environment of my framework and write JDO annotated classes in groovy, put the code into the JDO based database layer itself and thus be able to extend the object oriented storage at runtime. This is what adds very nicely to Stylesheets, JavaScript Codes, URL-Formats and Business Logic in Groovy in the Database layer resembling the dynamic part of Tangram. I tried this with the OpenJPA Enhancer and EclipseLink Weaver as well but with no success.
Also I now got a code base which was easily extended with another ORM Solution called EBean.  It was meant as an option with a smaller footprint but does not present any advantaged over the other options already implemented and proven in real projects live on the web using the Tangram dynamic web application framework.
So, anyone still thinks that byte-code transformation is a non-issue as you may read on introductory web pages on ORM? Give me some 30s to make your build process break - at least every once in a while when you don't expect it and won't easily discover the source of your pain.
But in the end with my Gradle based plugin, things are definitely a lot easier and reliable - again after a lot of work with things that were supposed to be easy, automatic, or hidden from me.

Donnerstag, 21. November 2013

Gradle Plugin for JPA, JDO, and EBean Bytecode Transformers

The plugin referenced here is meant for use with the tangram framework but there are a few calls which are helpfull for any user of Gradle and
It simply has easy to use wrappers for the enhancer/wever components for the persistence frameworks above since the solutions presented by those projects each for its own reason needed some cosmetic enhancement to fit into my gradle builds.

Prerequisites

Of course you will have some classes with persistence annotations. I'm assuming that you have a (sub)module containing the model classes for the API to be used (JDO, JPA with OpenJPA, DataNucleus, or EclipseLink, or Ebean with JPA annotations) which get compiled by some task of your project.

Preparation

The plugin can be obtained from the tangram snapshots repository.

// build.gradle
buildscript {
  repositories {
    mavenCentral()
    maven { url "http://repository-tangram.forge.cloudbees.com/snapshot" }
  }
  dependencies {
    classpath "tangram:gradle-plugin:0.9-SNAPSHOT"
  }
}


And of course it must be applied

apply plugin: 'tangram'

When using EclipseLink the bytecode transformer called weaver will already be contained in the compile dependencies of your module. The same applies for OpenJPA except that here the bytecode transformer is called enhancer.
For DataNucleus und Ebean the bytecode transformer is called enhancer again and is a dependency of the plugin itself. There is no generic need to have it in the compile dependencies of your module.
So in short, you will not have to modify your dependencies and the resulting package will not contain any additional classes or jars.

Solution

The plugin does not introduce any new tasks but just some methods that can be placed anywhere in the build process. In a standard gradle task-wiring of the java plugin the methods the following locations make sense:

// JDO with DataNucleus
compileJava.doLast {
  nucleusJdoEnhance()
}


// JPA with DataNucleus
compileJava.doLast {
  nucleusJpaEnhance()
}


// JPA with OpenJPA
jar.doFirst {
  openjpaEnhance()
}


// Ebean
compileJava.doLast { 
  ebeanEnhance()
}


// JPA with EclipseLink
compileJava.doLast {

  eclipselinkWeave()
}


But you may decide to use it at other points within your build process.

Background

For OpenJPA the wrapper presented here is a simple wrapper for the ant tasks provided by this project wired up for the given gradle build setup.
For EclipseLink the weaver the jar is included as a dependency for the plugin itself and the weaver is called called via its Java API. So woven codes can be generated independent of the using projects build setup.
For DataNucleus and Ebean the solution is somewhat more complicated since the enhancers are in separate jars which you most likely don't want to include in your resulting packages.
So those jars are also included as a dependency of the plugin, and again the plugin itself calls the Java APIs of the enhancers directly.

Sonntag, 10. November 2013

Splitter im Frühling

(English summary at the end)
Am Ende dieses Beitrags kommt eine universeller Konfigurations-Helper für das Springframework heraus.
org/tangram/spring/PropertySplittingPlaceholderConfigurer.java
Aber warum man so etwas brauchen könnte, wollte ich kurz an zwei oder vier (je nachdem wie man es zählen möchte) zeigen.
Bisher habe ich das Springframework und die jeweiligen Persistenzschicht immer komplett unabhängig voneinander genutzt: Java Persistence API (JPA) konfiguriert man über eine persistence.xml und Java Data Objects (JDO) über eine jdoconfig.xml. ORM Integrationen habe ich auch im zusammenspiel mit Spring MVC nicht benötigt. - Dachte ich.
Aber insbesondere durch Cloud-Umgebungen habe ich nun lernen müssen, daß der Weg über diese Dateien eigentlich nicht gerade "best practice" ist und eher für einfache Situationen taugt.
Wenn man dann endlich neben der Nutzung der Google App Engine auch mal einen Ausflug nach Cloudbees und OpenShift macht, tritt nämlich ein kleines Problem zutage: Die Werte in den oben genannten Dateien können nicht, wie alles andere, das ich in Spring "zusammenstecke", mit Platzhalter versehen werden, die erst zur Laufzeit des Systems aufgelöst werden.
Durch diese Ersetzung, wie sie z.B. Bei Spring quasi automatisch passiert - paßt sich ein ein grundsätzlich vorkonfiguriertes System dann in seine Laufzeitumgebung ein. - Bis auf die Persistenzschicht in meinem Fall.
Als einfachstes Beispiel nehmen wir mal die Verbindungsdaten zu einer Datenbank unter OpenShift. Diese sollte man am sinnvollsten aus den Umgebungsvariablen lesen, sagt die "best practice" von OpenShift.

JDO auf OpenShift

Also nimmt man beim Einsatz von JDO die Werte

<persistence-manager-factory name="transactions-optional">
  <property name="javax.jdo.PersistenceManagerFactoryClass"

            value="org.datanucleus.api.jdo.JDOPersistenceManagerFactory"/>     
  <property name="javax.jdo.option.ConnectionURL"

            value="mongodb://localhost:8111/db"/>
  <property name="javax.jdo.option.ConnectionUserName" value="u"/>
  <property name="javax.jdo.option.ConnectionPassword" value="p"/>
</persistence-manager-factory>


aus der jdoconfig.xml komplett heraus und übergibt sie bei der Instanziierung der PersistenceManagerFactory mit:

factory = 
JDOHelper.getPersistenceManagerFactory(jdoConfigOverrides, 
                                       "transactions-optional");

und diese jdoConfigOverrides bezieht man dann aus der Spring-Configuration, wo sie von den Ersetzungen auf Basis von Umgebungswerten profitieren:

<bean id="jdoConfigOverrides" class="java.util.HashMap">
  <constructor-arg>
    <map>
      <entry key="javax.jdo.option.ConnectionURL"

      value="mongodb://${OPENSHIFT_MONGODB_DB_HOST}:${OPENSHIFT_MONGODB_DB_PORT}/test"/>
      <entry key="javax.jdo.option.ConnectionUserName"

             value="${OPENSHIFT_MONGODB_DB_USERNAME}"/>
      <entry key="javax.jdo.option.ConnectionPassword"

             value="${OPENSHIFT_MONGODB_DB_PASSWORD}"/>
    </map>
  </constructor-arg>
</bean>

JPA auf OpenShift

Entsprechend geht man bei JPA vor und nimmt die Werte

<persistence-unit name="openjpa" transaction-type="RESOURCE_LOCAL">
  <provider>org.apache.openjpa.persistence.PersistenceProviderImpl</provider>
  <exclude-unlisted-classes>false</exclude-unlisted-classes>
  <properties>
    <property name="javax.persistence.jdbc.url"

              value="jdbc:postgresql://localhost:5432/postgres"/>
    <property name="javax.persistence.jdbc.user" value="un"/>
    <property name="javax.persistence.jdbc.password" value="pw"/>
  </properties>
</persistence-unit>

auch hier aus der Konfigurationsdatei heraus und fügt sie in die Spring-Konfiguration ein:

<bean id="jpaConfigOverrides" class="java.util.HashMap">
  <constructor-arg>
    <map>
      <entry key="
javax.persistence.jdbc.url"
  value="jdbc:postgres://${OPENSHIFT_POSTGRESQL_DB_HOST}:${OPENSHIFT_POSTGRESQL_DB_PORT}/db"/>
      <entry key="
javax.persistence.jdbc.user"
             value="${OPENSHIFT_POSTGRESQL_DB_USERNAME}"/>
      <entry key="
javax.persistence.jdbc.password"
             value="${OPENSHIFT_POSTGRESQL_DB_PASSWORD}"/>
    </map>
  </constructor-arg>
</bean>

denn auch hier gibt es entsprechende Parameter beim Erzeugen in diesem Fall der EntityManagerFactory:

factory = 
Persistence.createEntityManagerFactory(persistenceUnitName,
                                       jpaConfigOverrides);

Cloudbees

Über die Vorgehensweise hier stolperte ich erst, als ich meine Anwendungen mit Tangram auf OpenShift betreiben wollte, nachdem sie bei cloudbees schon liefen, da es für MySQL auf run@cloudbees eine "managed" Lösung mit einer DataSource gibt

 <!-- jndi datasource example (run@cloudbees) -->
 <property name="datanucleus.ConnectionFactoryName" 

           value="java:comp/env/jdbc/mydb" />

Das Problem besteht also mit der "hauseigenen" MySQL Datenbank dort überhaupt nicht.
Aber auch in dieser Umgebung werden ganz allgemein Werte der Betriebsumgebung an die Anwendungen durchgereicht und sollten von dieser auch benutzt werden.

URL Splitting in der Spring-Konfiguration

Das geht leider nicht ganz genau wie oben beschrieben, da unter Cloudbees z.B. für MongoDB die Verbindungdaten in einer URL übergeben werden (die gibt es auf OpenShift auch, aber man kann dort auch direkt auf die Einzelteile zurückgreifen).
Die Lösung ist hier mit ein wenig Programmieraufwand (s.o.) verbunden, da ich mich entschlossen habe, die Property-Ersetzungen durch Spring an dieser Stelle ein wenig aufzubohren und ganz allgemein URLs in ihren Teilen nutzbar zu machen.
Den

<bean id="propertyConfigurer" 
  class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
  <property name="locations">
    <list>
      <value>classpath*:/tangram/*.properties</value>
      <value>/WEB-INF/tangram/*.properties</value>
    </list>
  </property>
</bean>

ersetze ich also durch einen eigenen

<bean id="propertyConfigurer" 
      class="org.tangram.spring.PropertySplittingPlaceholderConfigurer">
  <property name="locations">
    <list>
      <value>classpath*:/tangram/*.properties</value>
      <value>/WEB-INF/tangram/*.properties</value>
    </list>
  </property>
</bean>

Danach steht von jeder URL, wie z.B. ${MONGOHQ_URL_MYBD} für den Service MongoHQ auf Cloudbees mit der verbundenen Datenbank MYDB, die Teile zur Verfügung:

<bean id="jdoConfigOverrides" class="java.util.HashMap">
  <constructor-arg>
    <map>
      <entry key="javax.jdo.option.ConnectionURL"

value="mongodb:${MONGOHQ_URL_TANGRAM.host}:${MONGOHQ_URL_MYBD.port}/${MONGOHQ_URL_MYDB.uri}" />
      <entry key="javax.jdo.option.ConnectionUserName" 

             value="${MONGOHQ_URL_MYDB.username}" />
      <entry key="javax.jdo.option.ConnectionPassword" 

             value="${MONGOHQ_URL_MYBD.password}" />
    </map>
  </constructor-arg>
</bean>

Das macht die Implementierung für alles, was sie für eine URL hält, und damit wird sie zu einem recht universellen Werkzeug bei der Spring-Konfiguration.
Jede URL in einer Property-Datei

# Example
url=mongodb://ruth:guessme@mongo.host:8111/db

wird zerlegt in

url.username=ruth
url.password=guessme
url.host=mongo.host
url.port=8111
url.uri=db 

Diese Platzhalter fügt der PlaceholderConfigurer dann an allen gewünschten Stellen ein. Das sollte für einen ganzen Bereich von Anwendungen erst einmal mit einem Werkzeug ausreichen.

English (sort of) Summary

For easier parameter passing through the springframework down into the JDO or JPA persistence layers of you app - especially on the plattforms of CloudBees and OpenShift - I introduced a PropertySplittingPlaceholderConfigurer, which splits everything it consideres a URL into the parts host, port, username, password, protocol, and uri.
So anything in the form of

# Example
url=mongodb://ruth:guessme@mongo.host:8111/db

gets exploded as if it would read

url.protocol=mongo
url.username=ruth
url.password=guessme
url.host=mongo.host
url.port=8111
url.uri=db

These generated properties can subsequently be used as placeholders in your springframework XML configuration files. So, what started as a little helper to connect to the databases of OpenShift in the best practice way, ended as a small but universal helper class.

Donnerstag, 29. Dezember 2011

Security Update "Low Priority" bei Datanucleus?

Nachdem die Software ja nun ein paar Monate auf github verfügbar ist, fallen in freier Wildbahn natürlich Kleinigkeiten auf, die im 0.7-Zweig bereinigt sind. Alle? Nicht ganz alle: Eine Kleinigkeit liegt ein wenig Außerhalb meines Einflußbereiches...
Der Bytecode "Enhancer" der datanucleus Implementierung von JDO funktioniert in der Fassung, wie ich sie für die Google App Engine und auch die RDBMS Variante als "Compiler Processor" benutze, nicht mit den aktuellen Versionen von Java, die seit Juni Verfügbar gemacht wurden. 1.6.0_26 ist die letzte Version, die noch läuft.
Mein Problem mit der Software fühlt sich so ähnlich an wie http://www.datanucleus.org/servlet/forum/viewthread_thread,6958 und die Antwort dort macht mir Sorge, denn es ist wohl nun auch der Java 6 Strang betroffen - und Security Updates wollen meine "Commercial Clients" alle gerne haben :-). Also für's Bauen bitte die o.g. Version benutzen oder den Enhancer auf aktuelle Software erweitern. Danke.
Auch wenn erfreuliche Zeichen aus der Google App Engine Welt zu hören sind und der Code zwischen RDBMS und GAE vereinheitlicht werden kann, bleibt also mit datanucleus 3.0.4 das Problem bestehen! Version  3.0.5 suche ich nocht zum Download.

Sonntag, 30. Oktober 2011

Beispielhaft

Leider fehlt mir immernoch der Ansatz, wie ich, ohne viel Code zu schreiben, Daten zwischen verschiedenen Datenbanksystemen austauschen kann. Ich hatte da schon auf vorgefertigte Techniken wie den XML-Export gehofft, die mir aber mit den internen IDs der Datenbanken bzw. von JDO Probleme machen.
Also habe ich das erst einmal aufgesteckt und statt dessen einfach die gute alte hsqldb eingesetzt, für die ich die Daten sehr einfach mitliefern kann. Dafür gibt's nun im rdbms Beispiel ein wenig Inhalt mit Velocity Templates und Groovy Code.
Das auf Gradle basierende Buildsystem kommt nun langsam in einen brauchbaren Zustand. Den CoreMedia CMS Adapter halte ich noch zurück, bis ich dort ein Beispiel für den Einsatz liefern kann.
Also: Beispiele und System stehen auf github bereit

Donnerstag, 26. Mai 2011

Vendor Lock In - III

Und nun wieder zurück zu dem Thema, welche Wege man sich verbaut und welche man sich offen halten möchte.
Bisher habe ich nur darüber geschrieben, daß ich mich auf Google App Engine wohlfühle und daß es nun belegt und testweise umgesetzt ist, daß es auch mit dedizierter Datenbank, JSP-Contaienr etc. geht.
Aber da war ja immernoch eine Ausbaustufe vorgesehen, die sich nicht darauf verläßt, daß JDO als ORM Layer benutzt wird. Letztlich läßt sich auf das Kernmodul alles aufsetzen, was irgendwelche Java Objekte erzeugt ,und mit diesen kann man dann objektorientiert Templates für die HTML Ausgabe schreiben.
Mein prominentes Beispiel war die (read only) Nutzung einer Datenbank, wie sie vom CoreMedia CMS beschrieben wurde. Exemplarisch habe ich das umgesetzt, nur mit den Richtext-Elementen habe ich noch so meine Probleme. Und für dieses Szenario gibt's nun tatsächlich ganz praktisch Systeme, für die das nützlich wäre. Mal sehen, ob sich endlich jemand "traut" diesen Weg zu gehen.
Die Lösung dient natürlich nicht dazu, ein CoreMedia CMS abzulösen. Die Kernkomponenten Content Management Server, Master Live Server, Workflow Server und Editoren bleiben notwendig. Wenn man aber unter Aufgabe aller Skalierungsoptionen eine datenausspielende Webanwendung mit dem vorhandenen Content umsetzen will, kann Tangram ein zusätzlicher Baustein sein.
Für diese Zusätzlichen Komponenten könnte der Deal dann so aussehen: Keine Lizenzkosten - Keine Performance.
Die Architektur ist einfach, hat keine zusätzlichen Serverkomponenten, benötigt ggf. die Replikation auf Datenbankebene und skaliert damit nicht ansatzweise so weit wie das "Original". Synergien mit Webanwendungen auf Basis der CAE / Unified API gibt es dann auch nicht mehr. Über Integrationen mit Suchmaschinen etc. habe ich mir nicht einmal große Gedanken gemacht. Was bleibt ist einfach, aber essenziell: Man bekommt seinen Content in's Web.