§TCP and UDP service lookup

ConductR offers an /etc/services style of experience for situations where you need to resolve a TCP or UDP port. Resolving ports at runtime is a good practice given that your application or service becomes de-coupled from the actual port in use.

The general idea is that you call a lookup function each time that you need to make a call to a service. Unfortunately many libraries that you use will not provide you with the opportunity to make that call, and so you may have to resort to an initial static lookup. The following example attempts to locate a fictitious JMS broker service:

// 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.lib.scala.ConnectionContext.Implicits.*

// Some URI help
import com.typesafe.conductr.bundlelib.scala.URI

// We also provide a cache designed specifically to
// hold location values. Caches are optional to the
// lookup, but they are encouraged.
val locationCache = LocationCache()

// ...and the lookup itself...
val jmsBroker = LocationService.lookup("/jms", URI("tcp://localhost:61616"), locationCache)

jmsBroker is typed Future[Option[URI]] meaning that an optional response with the resolved URI will be returned at some time in the future. Supposing that this lookup is made during the initialization of your program, the service you’re looking for may not exist. However calling the same function later may yield the service. This is because services can come and go.

Ideally, we would push the resolution of a service back on ConductR in a similar manner to how HTTP paths are resolved and use DNS for looking up services. Unfortunately DNS A-records do not yield a port number, and there is little library usage of DNS SRV-record types and, by extension, zeroconf.

§TCP and UDP static service lookup

Some bundle components cannot proceed with their initialization 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 work-around 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. Instead of blocking you may also consider using an Akka actor:

// bundlelib types are imported from com.typesafe.conductr.bundlelib.akka

// ImplicitConnectionContext is a convenience that we provide for obtaining
// a connection context within an actor.

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.

Next: Filesystems