https://github.com/forax/loom-actor
A small but fun actor framework based on loom
https://github.com/forax/loom-actor
actor actor-framework actor-library actor-model actor-system loom
Last synced: 16 days ago
JSON representation
A small but fun actor framework based on loom
- Host: GitHub
- URL: https://github.com/forax/loom-actor
- Owner: forax
- License: apache-2.0
- Created: 2021-12-09T17:06:23.000Z (about 4 years ago)
- Default Branch: master
- Last Pushed: 2024-05-17T14:53:38.000Z (over 1 year ago)
- Last Synced: 2024-05-17T16:14:06.077Z (over 1 year ago)
- Topics: actor, actor-framework, actor-library, actor-model, actor-system, loom
- Language: Java
- Homepage:
- Size: 49.8 KB
- Stars: 19
- Watchers: 2
- Forks: 7
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
- License: LICENSE
Awesome Lists containing this project
README
# Loom Actor
A small but fun actor framework based on loom
Loom is now fully integrated into the jdk 21
[https://jdk.java.net/](https://jdk.java.net/).
## How to use the API
The class `Actor` defines an actor like an actor in Erlang or Akka.
An actor uses a virtual thread (also called coroutine) to run, it consumes messages from dedicated message queue
and can post messages to other actors.
Unlike a traditional actor system, the messages are lambdas that do a method call so the actor API is
a class (or an enum, a record or even a lambda).
Moreover, the Actor API defines 3 different contexts
- the startup context, inside the consumer of `Actor.run(actor, code)`, the actions available are
`postTo(actor, message)` to post a message to an actor and `spawn(actor)` to spawn a new actor instance.
- the actor context, inside the `actor.behavior(factory)` of an actor, the actions available are
`currentActor(behaviorType)` to get the current actor, `panic(exception)` to report an exception,
`postTo(actor, message)` to post a message to an actor, `spawn(actor)`to spawn a new actor instance and
`shutdown()` to shutdown the current actor.
- the handler context, inside the `àctor.onSignal(signalHandler)`, the actions available are
`postTo(actor, message)` to post a message to an actor, `restart()` to restart an actor whith a fresh behavior
and `signal(actor, signal)` to send a signal message (an exception or a shutdown) to another actor.
During the development or tests, there is a debug mode `actor.debugMode(lookupMatcher, isImmutable)` that
checks that all posted messages are immutable. It has a runtime cost that why it's not enable by default.
### A simple example
Here is a simple example, `Actor.of()` create an actor, `behavior(factory)` defines the behavior, all the public
methods are message entry points. `Actors.run(actors, code)` spawn all the actors, run the code and
wait until all the actors are shutdown. In `Actor.run`, we post two messages to the actor, in response to the second
message "end", the actor shutdown itself, unblocking the method `Actor.run`.
```java
public class HelloMain {
public static void main(String[] args) throws InterruptedException {
record Hello(Context context) {
public void say(String message) {
System.out.println("Hello " + message);
}
public void end() {
context.shutdown();
}
}
var hello = Actor.of(Hello.class);
hello.behavior(Hello::new);
Actor.run(List.of(hello), context -> {
context.postTo(hello, $ -> $.say("actors using loom"));
context.postTo(hello, $ -> $.end());
});
}
}
```
### A JSON server example
Another example is an HTTP server using an actor to implement a REST API using JSON.
The request body and the response body are JSON message that are automatically converted using Jackson.
The annotations @RequestMapping, @RequestBody and @PathVariable works like their Spring Web counterparts.
The last parameter is the actor that should receive a message corresponding to the HTTP response.
[todo.html](todo.html) is the corresponding web application written in vanilla JavaScript.
To see the application, run the main and use a browser to visit `http://localhost:8080/todo.html`.
```java
public class HttpMain {
public static void main(String[] args) throws IOException, InterruptedException {
record Task(long id, String content) {}
record TaskWithoutId(String content) {}
record TaskController(Context context, List tasks) {
public static TaskController behavior(Context context) {
var tasks = new ArrayList();
// initial task
tasks.add(new Task(0, "Hello from an http server powered by loom !"));
return new TaskController(context, tasks);
}
@RequestMapping(path = "/tasks")
public void getAllTasks(Actor>> reply) {
System.err.println("getAllTasks");
context.postTo(reply, $ -> $.response(OK, tasks));
}
@RequestMapping(path = "/tasks", method = POST)
public void createTask(@RequestBody TaskWithoutId taskWithoutId, Actor> reply) {
System.err.println("createTask " + taskWithoutId);
var task = new Task(tasks.size(), taskWithoutId.content);
tasks.add(task);
context.postTo(reply, $ -> $.response(OK, task));
}
@RequestMapping(path = "/tasks/{id}", method = DELETE)
public void deleteTask(@PathVariable("id") String id, Actor> reply) {
System.err.println("deleteTask " + id);
var taskId = Integer.parseInt(id);
var removed = tasks.removeIf(task -> task.id == taskId);
if (!removed) {
context.postTo(reply, $ -> $.response(NOT_FOUND, null));
return;
}
context.postTo(reply, $ -> $.response(OK, null));
}
}
var actor = Actor.of(TaskController.class)
.behavior(TaskController::behavior)
.onSignal((signal, context) -> {
context.restart(); // restart if an error occurs
});
new HttpServer()
.routes(actor)
.bind(new InetSocketAddress("localhost", 8080));
}
}
```
There are more examples in the folder [examples](src/main/examples/com/github/forax/loom/actor/examples).
## How to build
Download the latest early access build of jdk 21 [http://jdk.java.net/](http://jdk.java.net/)
set the environment variable JAVA_HOME to point to that JDK and then use Maven.
```
export JAVA_HOME=/path/to/jdk
mvn package
```