Multiple Services and Dependency Injection
We've seen the configuration of machines and how to build up clusters. Now let's return to our app-server example and explore how more interesting services can be configured, composed, and combined.
Service Configuration
We'll begin by using more key-value pairs to configure the JBoss server to run a real app:
name: appserver-configured
services:
- type: org.apache.brooklyn.entity.webapp.jboss.JBoss7Server
brooklyn.config:
wars.root: https://search.maven.org/remotecontent?filepath=org/apache/brooklyn/example/brooklyn-example-hello-world-sql-webapp/0.12.0/brooklyn-example-hello-world-sql-webapp-0.12.0.war # BROOKLYN_VERSION
http.port: 8080
(As before, you'll need to add the location
info; localhost
will work for these and subsequent examples.)
When this is deployed, you can see management information in the Brooklyn Web Console,
including a link to the deployed application (downloaded to the target machine from the hello-world
URL),
running on port 8080.
Tip: If port 8080 might be in use, you can specify 8080+
to take the first available port >= 8080;
the actual port will be reported as a sensor by Brooklyn.
Multiple Services
If you explored the hello-world-sql
application we just deployed,
you'll have noticed it tries to access a database.
And it fails, because we have not set one up. Let's do that now:
name: appserver-w-db
services:
- type: org.apache.brooklyn.entity.webapp.jboss.JBoss7Server
name: AppServer HelloWorld
brooklyn.config:
wars.root: https://search.maven.org/remotecontent?filepath=org/apache/brooklyn/example/brooklyn-example-hello-world-sql-webapp/0.12.0/brooklyn-example-hello-world-sql-webapp-0.12.0.war # BROOKLYN_VERSION
http.port: 8080+
java.sysprops:
brooklyn.example.db.url:
$brooklyn:formatString:
- "jdbc:%s%s?user=%s&password=%s"
- $brooklyn:component("db").attributeWhenReady("datastore.url")
- visitors
- brooklyn
- $brooklyn:external("brooklyn-demo-sample", "hidden-brooklyn-password")
- type: org.apache.brooklyn.entity.database.mysql.MySqlNode
id: db
name: DB HelloWorld Visitors
brooklyn.config:
creation.script.password: $brooklyn:external("brooklyn-demo-sample", "hidden-brooklyn-password")
datastore.creation.script.template.url: https://github.com/apache/brooklyn-library/raw/master/examples/simple-web-cluster/src/main/resources/visitors-creation-script.sql
Here there are a few things going on:
- We've added a second service, which will be the database; you'll note the database has been configured to run a custom setup script
- We've injected the URL of the second service into the appserver as a Java system property (so our app knows where to find the database)
- We've used externalized config to keep secret information out of the blueprint; this is loaded at runtime from an externalized config provider, such as a remote credentials store
Caution: Be careful if you write your YAML in an editor which attempts to put "smart-quotes" in. All quote characters must be plain ASCII, not fancy left-double-quotes and right-double-quotes!
There are as many ways to do dependency injection as there are developers, it sometimes seems; our aim in Brooklyn is not to say this has to be done one way, but to support the various mechanisms people might need, for whatever reasons. (We each have our opinions about what works well, of course; the one thing we do want to call out is that being able to dynamically update the injection is useful in a modern agile application -- so we are definitively not recommending this Java system property approach ... but it is an easy one to demo!)
The way the dependency injection works is again by using the $brooklyn:
DSL,
this time referring to the component("db")
(looked up by the id
field on our DB component),
and then to a sensor emitted by that component.
All the database entities emit a database.url
sensor when they are up and running;
the attributeWhenReady
DSL method will store a pointer to that sensor (a Java Future under the covers)
in the Java system properties map which the JBoss entity reads at launch time, blocking if needed.
This means that the deployment occurs in parallel, and if the database comes up first,
there is no blocking; but if the JBoss entity completes its installation and
downloading the WAR, it will wait for the database before it launches.
At that point the URL is injected, first passing it through formatString
to include the credentials for the database (which are defined in the database creation script).
An Aside: Substitutability
Don't like JBoss? Is there something about MariaDB? One of the modular principles we follow in Brooklyn is substitutability: in many cases, the config keys, sensors, and effectors are defined in superclasses and are portable across multiple implementations.
Here's an example deploying the same application but with different flavors of the components:
name: appserver-w-db-other-flavor
services:
- type: org.apache.brooklyn.entity.webapp.tomcat.TomcatServer
name: AppServer HelloWorld
brooklyn.config:
wars.root: https://search.maven.org/remotecontent?filepath=org/apache/brooklyn/example/brooklyn-example-hello-world-sql-webapp/0.12.0/brooklyn-example-hello-world-sql-webapp-0.12.0.war # BROOKLYN_VERSION
http.port: 8080+
java.sysprops:
brooklyn.example.db.url:
$brooklyn:formatString:
- "jdbc:%s%s?user=%s&password=%s"
- $brooklyn:component("db").attributeWhenReady("datastore.url")
- visitors
- brooklyn
- $brooklyn:external("brooklyn-demo-sample", "hidden-brooklyn-password")
- type: org.apache.brooklyn.entity.database.mariadb.MariaDbNode
id: db
name: DB HelloWorld Visitors
brooklyn.config:
creation.script.password: $brooklyn:external("brooklyn-demo-sample", "hidden-brooklyn-password")
datastore.creation.script.template.url: https://github.com/apache/brooklyn-library/raw/master/examples/simple-web-cluster/src/main/resources/visitors-creation-script.sql
provisioning.properties:
minRam: 8192
By changing two lines we've switched from JBoss and MySQL to Tomcat and MariaDB.
We've also brought in the provisioning.properties
from the VM example earlier
so our database has 8GB RAM.
Any of those properties, including imageId
and user
, can be defined on a per-entity basis.