Note that this project is EOL as of 2018-01-31.
This project provides a number of libraries to facilitate ConductR's status service and its service lookup service. Note that usage of the libraries in your code is entirely benign when used outside of the context of ConductR i.e. you will find that your applications and services will continue to function normally when used without ConductR. We have also designed the libraries to be a convenience to ConductR's REST and environment variable based APIs, and to have a very low impact on your code.
Add one of the following libraries to your project.
"com.typesafe.conductr" % "java-conductr-bundle-lib" % "2.2.0"
"com.typesafe.conductr" %% "scala-conductr-bundle-lib" % "2.2.0"
"com.typesafe.conductr" %% "akka24-conductr-bundle-lib" % "2.2.0"
"com.typesafe.conductr" %% "akka25-conductr-bundle-lib" % "2.2.0"
"com.typesafe.conductr" %% "play25-conductr-bundle-lib" % "2.2.0"
"com.typesafe.conductr" %% "play26-conductr-bundle-lib" % "2.2.0"
"com.typesafe.conductr" %% "lagom13-java-conductr-bundle-lib" % "2.2.0"
"com.typesafe.conductr" %% "lagom13-scala-conductr-bundle-lib" % "2.2.0"
"com.typesafe.conductr" %% "lagom14-java-conductr-bundle-lib" % "2.2.0"
"com.typesafe.conductr" %% "lagom14-scala-conductr-bundle-lib" % "2.2.0"
Note that the examples here use the following import to conveniently build the JDK URI
and URL
types.
import com.typesafe.conductr.bundlelib.scala.{URL, URI}
- conductr-bundle-lib
- scala-conductr-bundle-lib
- akka[24|25]-conductr-bundle-lib
- play[25|26]-conductr-bundle-lib
- lagom[13|14]-java-conductr-bundle-lib
- lagom[13|14]-scala-conductr-bundle-lib
This library provides a base level of functionality mainly formed around constructing the requisite payloads for ConductR's RESTful services. The library is pure Java and has no dependencies other than the JDK.
Two services are covered by this library:
com.typesafe.conductr.bundlelib.LocationService
com.typesafe.conductr.bundlelib.StatusService
ConductR's location service is able to respond with a URI declaring where a given service (as named by a bundle component's endpoint) resides. The http payload can be constructed as follows:
HttpPayload payload = LocationService.createLookupPayload("someservice")
The HttpPayload
object may then be queried for elements that will help you make an http request. These methods are:
getUrl
getRequestMethod
getFollowRedirects
When processing the http response you should check for the following http status codes:
- 307 - temporary redirect - the service can be found at the location indicated by the
Location
header.Cache-Control
may also be supplied to indicate how long the location may be cached for. - 404 - not found - the service cannot be located at this time
You should also prepare for timing out on a request and process as per a 404.
Conduct's status service is required by a bundle component in order to signal when it has started. A successful startup is anything that the application is required to do to become available for processing. For example, this may involved validating configuration. The http payload can be constructed as follows:
HttpPayload payload = StatusService.createSignalStartedPayload()
The HttpPayload
object may then be queried for elements in the same way as for the location service of the previous section.
When processing the http response you should check for the following http status codes:
- 2xx - success - any 200 series response is a success meaning that ConductR has successfully acknowledged the startup signal
- xxx - failure - anything else constitutes an error and you should cause the bundle component to exit
You should also prepare for timing out on a request and exit the bundle component if this occurs.
This library provides a reactive API using only Scala and Java types. There are no dependencies other than conductr-bundle-lib
, Scala and the JDK and it is designed to be used where there is no Akka or Play dependency in your application.
As with conductr-bundle-lib
there are two services:
com.typesafe.conductr.bundlelib.scala.LocationService
com.typesafe.conductr.bundlelib.scala.StatusService
Please read the section on conductr-bundle-lib
for an introduction to these services.
The LocationService looks up service names and processes HTTP's 307
"temporary redirect" responses to return the location of the resolved service (or a 404
if one cannot be found). Many HTTP clients allow the following of redirects, particularly when either of the HEAD
or GET
methods are used (other methods may be considered insecure by default). Therefore if the service you are locating is an HTTP one then using a regular HTTP client should require no further work. Here is an example of using the Dispatch library:
val svc = LocationService.getLookupUrl("someservice", URL("http://127.0.0.1:9000/someservice"))
val svcResp = Http.configure(_.setFollowRedirects(true))(url(svc.toString).OK)
The above declares an svc
val which will either be the one that ConductR provides, or one to use for development running on your machine.
When using HTTP clients, consider having the client cache responses. ConductR will return Cache-Control header information informing the client how to cache.
If the service you require is not HTTP based then you may use the LocationService.lookup
function. The following code illustrates how a service may be located in place of creating and dispatching your own payload. The sample also shows how to use a cache provided specifically for these lookups (note use com.typesafe.lib.scala for 1.2 of this library onwards):
// This will require an implicit ConnectionContext to
// hold a Scala ExecutionContext. There are different
// ConnectionContexts depending on which flavor of the
// library is being used. For the Scala flavor, a Scala
// ExecutionContext is composed. The ExecutionContext
// is needed as "service" is returned as a Future.
// For convenience, we provide a global ConnectionContext
// that may be imported.
import com.typesafe.conductr.bundlelib.scala.ConnectionContext.Implicits.global
val locationCache = LocationCache()
val service = LocationService.lookup("someservice", URI("tcp://localhost:1234"), locationCache)
service
is typed Future[Option[URI]]
meaning that an optional URI response will be returned at some time in the future. Supposing that this lookup is made during the initialisation of your program, the service you're looking for may not exist. However calling the same function later on may yield the service. This is because services can come and go. Note that the fallback URI of "tcp://localhost:1234"
will be returned if this function is called upon when started outside of ConductR.
The service response constitutes a URI that describes its location.
Some bundle components cannot proceed with their initialisation unless the service can be located. We encourage you to re-factor these components so that they look up services at the time when they are required, given that services can come and go. However if you are somehow stuck with this style of code then you may consider the following blocking code as a temporary measure:
val resultUri = Await.result(
LocationService.lookup("someservice", URI("http://127.0.0.1:9000"), locationCache),
sometimeout)
val serviceUri = resultUri.getOrElse(System.exit(70))
In the above, the program will exit if a service cannot be located at the time the program initializes; unless the program has not been started by ConductR in which case an alternate URI is provided.
The following code illustrates how your bundle component should register its initial health with ConductR. Calling this function is to be done in place of creating and dispatching your own payload:
StatusService.signalStartedOrExit()
In general, the return value of signalStartedOrExit
is not used and your program proceeds. If ConductR fails to reply, or replies with an error status then this bundle component will exit.
In case you are interested, the function returns a Future[Option[Unit]]
where a future Some(())
indicates that ConductR has successfully acknowledged the startup signal. A future of None
indicates that the bundle has not been started by ConductR.
This library provides a reactive API using Akka Http and should be used when you are using Akka. The library depends on scala-conductr-bundle-lib
and can be used for both Java and Scala.
As with conductr-bundle-lib
there are these two services:
com.typesafe.conductr.bundlelib.akka.LocationService
com.typesafe.conductr.bundlelib.akka.StatusService
and there is also another:
com.typesafe.conductr.bundlelib.akka.Env
Please read the section on conductr-bundle-lib
and then scala-conductr-bundle-lib
for an introduction to these services. The Env
one is discussed in the "Akka Clustering" section below.
Other than the import
s for the types, the only difference in terms of API are usage is how a ConnectionContext
is established. A ConnectionContext
for Akka requires an implicit ActorSystem
or ActorContext
at a minimum e.g.:
implicit val cc = ConnectionContext()
There is also a lower level method where the HttpExt
and ActorMaterializer
are passed in:
implicit val cc = ConnectionContext(httpExt, actorMaterializer)
When in the context of an actor, a convenient ImplicitConnectionContext
trait may be mixed in to establish the ConnectionContext
. The next section illustrates this in its sample MyService
actor.
As a reminder, some bundle components cannot proceed with their initialisation unless the service can be located. We encourage you to re-factor these components so that they look up services at the time when they are required, given that services can come and go. That said, here is a non-blocking improvement on the example provided for the scala-conductr-bundle-lib
:
class MyService(cache: CacheLike) extends Actor with ImplicitConnectionContext {
import context.dispatcher
override def preStart(): Unit =
LocationService.lookup("someservice", URI("http://127.0.0.1:9000"), cache).pipeTo(self)
override def receive: Receive =
initial
private def initial: Receive = {
case Some(someService: URI) =>
// We now have the service
context.become(service(someService))
case None =>
self ! PoisonPill
}
private def service(someService: URI): Receive = {
// Regular actor receive handling goes here given that we have a service URI now.
...
}
}
This type of actor is used to handle service processing and should only receive service oriented messages once its dependent service URI is known. This is an improvement on the blocking example provided before, as it will not block. However it still has the requirement that someservice
must be running at the point of initialization, and that it continues to run. Neither of these requirements may always be satisfied with a distributed system.
The following example illustrates how status is signalled using the Akka Java API:
ConnectionContext cc = ConnectionContext.create(system);
StatusService.getInstance().signalStartedOrExitWithContext(cc);
Similarly here is a service lookup:
ConnectionContext cc = ConnectionContext.create(system);
LocationService.getInstance().lookupWithContext("whatever", URI("tcp://localhost:1234"), cache, cc)
Akka cluster based applications or services have a requirement where the first node in a cluster must form the cluster, and the subsequent nodes join with any of the ones that come before them (seed nodes). Where bundles share the same system
property in their bundle.conf
, and have an intersection of endpoint names, then ConductR will ensure that only one bundle is started at a time. Thus the first bundle can determine whether it is the first bundle, and subsequent bundles can determine the IP and port numbers of the bundles that have started before them.
In order for an application or service to take advantage of this guarantee provided by ConductR, the following call is required to obtain configuration that will be used when establishing your actor system:
import com.typesafe.conductr.bundlelib.akka.Env
import com.typesafe.conductr.lib.akka.ConnectionContext
...
val systemName = Env.mkSystemName("MyApp1")
val config = Env.asConfig(systemName)
implicit val system = ActorSystem(systemName, config.withFallback(ConfigFactory.load()))
Clusters will then be formed correctly. The above call looks for an endpoint named akka-remote
by default. Therefore if you must declare the Akka remoting port as seed. The following endpoint declaration within a build.sbt
shows how:
BundleKeys.endpoints := Map("akka-remote" -> Endpoint("tcp"))
In the above, no declaration of services
is required as akka remoting is an internal, cluster-wide TCP service.
If you are using Play 2.5 or 2.6 then this section is for you.
sbt-conductr is automatically adding this library to your Play project.
This library provides a reactive API using Play WS and should be used when you are using Play. The library depends on akka24-conductr-bundle-lib
and can be used for both Java and Scala. As per Play's conventions, play.api
is used for the Scala API and just play
is used for Java.
As with conductr-bundle-lib
there are two services:
com.typesafe.conductr.bundlelib.play.LocationService
(Java) orcom.typesafe.conductr.bundlelib.play.api.LocationService
(Scala)com.typesafe.conductr.bundlelib.play.StatusService
(Java) orcom.typesafe.conductr.bundlelib.play.api.StatusService
(Scala)
and there is also another:
com.typesafe.conductr.bundlelib.play.Env
(Java) orcom.typesafe.conductr.bundlelib.play.api.Env
(Scala)
Please read the section on conductr-bundle-lib
and then scala-conductr-bundle-lib
for an introduction to these services. The Env
one is discussed in the section below. The major difference between the APIs for Play 2.5 and the other variants is that components are expected to be injected. For example, to use the LocationService
in your controller (Scala):
class MyGreatController @Inject() (locationService: LocationService, locationCache: CacheLike) extends Controller {
...
locationService.lookup("known", URI(""), locationCache)
...
}
The following components are available for injection:
- CacheLike
- ConnectionContext
- LocationService
- StatusService
Note that if you are using your own application loader then you should ensure that the Akka and Play ConductR-related properties are loaded. Here's a complete implementation (for Scala):
class MyCustomApplicationLoader extends ApplicationLoader {
def load(context: ApplicationLoader.Context): Application = {
val systemName = AkkaEnv.mkSystemName("application")
val conductRConfig = Configuration(AkkaEnv.asConfig(systemName)) ++ Configuration(PlayEnv.asConfig(systemName))
val newConfig = context.initialConfiguration ++ conductRConfig
val newContext = context.copy(initialConfiguration = newConfig)
val prodEnv = Environment.simple(mode = Mode.Prod)
new GuiceApplicationLoader(GuiceApplicationBuilder(environment = prodEnv)).load(newContext)
}
}
If you are using Lagom 1.x with Java, then this section is for you.
sbt-conductr automatically adds this library to your Lagom project. You don't need set any additional settings for your Lagom services.
Maven users should add the dependency directly to your Lagom service implementation projects. For example:
<dependency>
<groupId>com.typesafe.conductr</groupId>
<artifactId>lagom14-java-conductr-bundle-lib_2.11</artifactId>
<version>2.1.1</version>
</dependency>
Note that if you are using your own application loader then you should ensure that the Akka, Play and Lagom ConductR-related properties are loaded. Here's a complete implementation (in Scala):
import com.typesafe.conductr.bundlelib.akka.{ Env => AkkaEnv }
import com.typesafe.conductr.bundlelib.play.api.{ Env => PlayEnv }
import play.api._
import play.api.inject.guice.{ GuiceApplicationBuilder, GuiceApplicationLoader }
class MyCustomApplicationLoader extends ApplicationLoader {
def load(context: ApplicationLoader.Context): Application = {
val systemName = AkkaEnv.mkSystemName("application")
val conductRConfig = Configuration(AkkaEnv.asConfig(systemName)) ++ Configuration(PlayEnv.asConfig(systemName))
val newConfig = context.initialConfiguration ++ conductRConfig
val newContext = context.copy(initialConfiguration = newConfig)
val prodEnv = Environment.simple(mode = Mode.Prod)
new GuiceApplicationLoader(GuiceApplicationBuilder(environment = prodEnv)).load(newContext)
}
}
If you are using Lagom 1.x with Scala, then this section is for you.
sbt-conductr automatically adds this library to your Lagom project. This provides components, including the service locator and other components necessary for initialization on ConductR, which you can mix in with your application cake. To do so, mix ConductRApplicationComponents
into your production cake:
import com.lightbend.lagom.scaladsl.devmode.LagomDevModeComponents
import com.lightbend.lagom.scaladsl.server._
import com.typesafe.conductr.bundlelib.lagom.scaladsl.ConductRApplicationComponents
class HelloApplicationLoader extends LagomApplicationLoader {
override def load(context: LagomApplicationContext) =
new HelloApplication(context) with ConductRApplicationComponents
override def loadDevMode(context: LagomApplicationContext) =
new HelloApplication(context) with LagomDevModeComponents
override def describeService = Some(readDescriptor[HelloService])
}
Once you have added this to each of your services, you should be ready to run in ConductR. Also note that it’s very important to implement the describeService
method on LagomApplicationLoader
, as this will ensure that the ConductR sbt tooling is able to correctly discover the Lagom service APIs offered by each service. If using a version of Lagom earlier than 1.3.6, you should implement describeServices
(which returns immutable.Seq[Descriptor]
) instead of describeService
, however returning more than one service descriptor from describeServices
is not supported by ConductR.
You'll need permissions to release to the typesafe.com organization at Sonatype. You will also require a PGP key.
This projects uses sbt-release
. To release use the release
command (no +
required as a prefix). This will cross publish releases for Scala. Note that you will see messages like this:
[trace] Stack trace suppressed: run last conductRBundleLib/*:publish for the full output.
[trace] Stack trace suppressed: run last common/*:publish for the full output.
[error] (conductRBundleLib/*:publish) java.io.IOException: destination file exists and overwrite == false
[error] (common/*:publish) java.io.IOException: destination file exists and overwrite == false
This is because the same Java libraries are used for multiple Scala versions. Do not be concerned, the publishing tool is actually just warning you i.e. they are not errors.
Everything is released to Maven Central via the Sonatype repository, including automated staging to release.