Wookiee 2.4: Unveiling a New Hope in Microservices

New and Improved Wookiee 2.4

Harnessing the Power of Change: Wookiee 2.4’s Revolutionary Leap

In the ever-evolving landscape of microservices, Wookiee has stood out as a framework that simplifies the complex. With the release of Wookiee 2.4, we are stepping into a future beyond Akka, introducing a suite of innovative features designed to simplify and empower your microservices architecture. This release marks a significant shift; with a focus on user-centric improvements, interoperability within the Wookiee framework, and an eye towards evolving needs.

Support for an Akka-less Future

Wookiee 2.4 initiates a decisive move away from Akka, not completely replacing it yet, but laying a solid foundation for a future without it. And most importantly, opening an escape hatch for users who want to eliminate Akka from their own implementations. This version introduces tools and patterns that ensure all functionalities previously dependent on Akka are now available through new, streamlined processes. These changes bring a newfound flexibility and simplicity to Wookiee’s already robust capabilities.

Wookiee Actors

The WookieeActor is a crucial breakthrough in this version, designed to replicate Akka actor behaviors without the dependency on Akka itself. This feature ensures a smoother transition by allowing almost a 1:1 swap with existing Akka actors, minimizing the learning curve and easing migration. For instance, creating a WookieeActor is straightforward:

import com.oracle.infy.wookiee.actors.WookieeActor 

class MyActor extends WookieeActor { 
    override def receive: Receive = { 
        case "hello" => 
            sender() ! "world" 
    } 
} 

val myActor = WookieeActor.actorOf(new MyActor)

Note that every capability of Akka actors is available for usage within a WookieeActor, including structures like: become(receiveBlock), schedule(actor, message), asks (e.g. actor ? message), schedulers (e.g. scheduleOnce(10.seconds, actor, message)), and routers (e.g. WookieeActor.withRouter(new MyActor)) to name a few. The WookieeActor offers a familiar yet simplified approach to managing concurrency and state within your services.

The Mediator Model

Central to this transition is the introduction of the Mediator trait. This innovative feature allows for static object management, enabling global access to various classes and resources in a manner reminiscent of Akka’s actor system. The Mediator simplifies the management of service instances, particularly beneficial in testing environments where multiple Wookiee instances may coexist. Since we no longer plan to have a global actor system, we rely on a simple String instance-id to separate different instances of Wookiee. Having access to this instance-id (which will be present in the base TypeSafe Config available across Wookiee) allows static access to objects stored in a Mediator from anywhere.

import com.oracle.infy.wookiee.Mediator

object MediatedObject extends Mediator[String]

MediatedObject.registerInstance("some-instance", "string-to-store")
MediatedObject.getMediator("some-instance") // Will be "string-to-store"

Usually interacting with a Mediator will simply involve passing along the config. In this example we assume that MediatedObject.registerInstance(config, “some-string”) was already called elsewhere:

class MyService(config: Config) extends ServiceV2(config) {
    val mediatedString = MediatedObject.getMediator(config)
}

Enhanced Service and Component Management

ServiceV2 and ComponentV2 are other key introductions. These provide the functionality to create services and components without relying on Akka, complete with full monitoring capabilities. This advancement not only eases the transition for current Wookiee users but also opens doors for new users to onboard with less complexity.

class MyService(config: Config) extends ServiceV2(config)
class MyComponent(name: String, config: Config) extends ComponentV2(name, config)

wookiee-discovery: A New Era of Clustering and Messaging

wookiee-discovery introduces a way to enable services to communicate within a cluster, abstracting the complexity of the underlying gRPC implementation. It is a groundbreaking module designed to replace Akka remoting and clustering for inter-service communication.

Discoverable Commands

The core of wookiee-discovery lies in its DiscoverableCommand trait, which simplifies the process of making services discoverable and communicable across a cluster. This trait makes it possible to extend services with minimal effort, enabling them to participate in the cluster’s messaging infrastructure seamlessly.

Creating a discoverable command involves implementing the DiscoverableCommand interface, which allows you to define custom behaviors that can be triggered across the service cluster:

import com.oracle.infy.wookiee.discovery.command.DiscoverableCommand 

case class InputHolder(input: String)
case class OutputHolder(output: String)

class MyCommand extends DiscoverableCommand[InputHolder, OutputHolder] {                   
    override def commandName: String = "my-command" 
    override def execute(args: InputHolder): Future[OutputHolder] = Future {           
        OutputHolder(args.input)
    } 
}

// In ServiceV2 or ComponentV2 class
DiscoverableCommandHelper.registerDiscoverableCommand(new MyCommand)

Command Execution

Executing a discoverable command across the cluster is even simpler, showcasing wookiee-discovery‘s power and flexibility:

DiscoverableCommandExecution.executeDiscoverableCommand[InputHolder, OutputHolder](zkConfig, "my-command", InputHolder("input"))

This module significantly reduces the complexity of Wookiee-to-Wookiee communication, making it more accessible and manageable. Services can register themselves and become effortlessly discoverable, enhancing the connectivity and scalability of your microservices architecture. Additional examples of this usage can be found in the ‘advanced-communications’ example project.


wookiee-web: Streamlining HTTP and WebSocket Interactions

Wookiee 2.4 brings the wookiee-web component, a significant upgrade to hosting and handling HTTP and WebSocket calls. Its integration with Helidon ensures high performance and reliability, catering to the demanding needs of modern web services.

HTTP Endpoints

Creating HTTP endpoints is intuitive with wookiee-web. Developers can define endpoints by extending the HttpCommand class, offering a clear and concise way to handle HTTP requests:

import com.oracle.infy.wookiee.component.web.http.HttpCommand 

class ExternalHttpCommand extends HttpCommand { 
    override def method: String = "GET" 
    override def path: String = "/external/$value" 
    override def execute(input: WookieeRequest): Future[WookieeResponse] = { 
        // Business logic 
    } 
} 

// In ServiceV2 or ComponentV2 class
WookieeEndpoints.registerEndpoint(new ExternalHttpCommand)

After calling the registerEndpoint(..) method this HTTP endpoint will be available on its configured ports. For other ways to create these endpoints, including a functional signature, reference the README here.

WebSocket Endpoints

Creating WebSocket endpoints is intuitive and efficient. A WebSocket endpoint can be defined and managed with minimal code, offering robust functionality:

import javax.websocket.Session

case class AuthHolder(username: String, password: String)

class ExternalWSHandler(implicit conf: Config, ec: ExecutionContext) extends WookieeWebsocket[AuthHolder] {
    override def path: String = "/external/$somevalue"

    override def handleText(text: String, request: WookieeRequest, authInfo: Option[AuthHolder])(
        implicit session: Session
    ): Unit = {
        reply(s"Got message: [$text]")
    }
}

// In ServiceV2 or ComponentV2 class
WookieeEndpoints.registerWebsocket(new ExternalWSHandler)

wookiee-kafka: Simplifying Kafka Integration

The new library wookiee-kafka brings a convenient approach to Kafka management; offering lightweight methods to start producers and consumers, manage topics, and even facilitate testing with a local Kafka server. This module democratizes Kafka integration, making it accessible and manageable for developers of all levels. It provides a unified and elegant approach to managing Kafka interactions, eliminating the need for repetitive and complex code.

Local Kafka Server

For testing purposes, spinning up a local Kafka server is straightforward:

import com.oracle.infy.wookiee.kafka.testing.model._

// Start up a zookeeper instance
val zkMode: ZooMode = TestingServerMode()
val (kafkaPort, closeable) = WookieeKafka.startLocalKafkaServer(zkMode.getConnectString)

Kafka Producers and Consumers

Setting up Kafka producers and consumers is also greatly simplified:

import com.oracle.infy.wookiee.kafka.WookieeKafka 

val producer = WookieeKafka.startProducer(s"localhost:$kafkaPort") 
producer.send("topic-name", "some-message") 

val consumer = WookieeKafka.startConsumer(s"localhost:$kafkaPort", "group-id") 
consumer.subscribe("topic-name")

These examples show how wookiee-kafka makes it easier to work with Kafka, reducing the amount of boilerplate code and providing a more intuitive API. Dive into the WookieeKafka helper object to see what other helpful conveniences are available.

Join the Resistance: Elevate Your Services with Wookiee 2.4

Wookiee 2.4 represents a significant leap forward towards a more adaptable, user-friendly, and integrative microservice architecture. With its user-centric design and innovative instruments, it stands poised to redefine how applications are built and managed. By embracing these new features, developers can build more robust and scalable services with ease.

We invite you to explore the innovative landscape of Wookiee 2.4. Delve into the ‘advanced-communications’ project for practical examples of these new features in action. Your insights and contributions are invaluable as we continue to shape the future of Wookiee. Join us in this exciting journey and be part of a pioneering community redefining the microservices framework.


Share