nactio / nact Goto Github PK
View Code? Open in Web Editor NEWnact ⇒ node.js + actors ⇒ your services have never been so µ
Home Page: https://nact.xyz
License: Apache License 2.0
nact ⇒ node.js + actors ⇒ your services have never been so µ
Home Page: https://nact.xyz
License: Apache License 2.0
At Root, we've been using nact in production. One feature of the library we haven't really been using is logging. I would like to propose removing it in the interest of reducing the maintenance burden and increasing simplicity of code. Extension points could be opened up to allow logging to be added posthoc in a separate codebase.
The base nact library is focusing on making a solid set of primitives.
These primitives are not enough however as users will likely find themselves implementing the same patterns over and over again.
Thus there should be another repository which abstracts and documents these patterns and makes them accessible to users of nact.
Given nact's design, it would make sense to create a declarative means of specifying when and how frequently snapshots of an actor state should occur. This could be part of an optional configuration parameter. An example of how this would look might be:
const { every } = require('nact');
spawnPersistent(parent, (state, msg, ctx)=> msg+state, 'key', 'name', { createSnapshot: every(6).minutes 'min') });
This is problematic as the persistence engine uses soft delete which means that if the last event is marked as deleted, it will fall over in attempting to persist the next event.
setImmediate is used in Nact to implement non blocking macro task semantics as it is more performant for this use case than setTimeout (which in many implementations has a minimum duration). However this API is not available in most browsers. Polyfilling this API may allow Nact to work in browser
Hey Nick, project looks interesting! I wanted to ask what are good use cases for nact. I see that you're working on a postgres persistence plugin, I could work on a MongoDB one. I've got some time in my hands and am looking for interesting stuff to do.
Starting a new nact project should be super quick and fast.
If we offer a project scaffold which adopts a convention driven approach, it'll allow devs to be more immediately productive.
Given,
class Result<T> {
constructor (promise: Promise<T>) {
this.promise = promise;
}
public then(...args) {
return this.promise.then(...args);
}
public reduce(...args) {
return this.promise.then(result => result.reduce(...args));
}
}
Q. What type of parameters and return of the functions: then()
, reduce()
should be?
onCrash
allows you to react to the message that may have caused the crash, however there may be messages in nact's internal queue for that actor which will after the crash be silently ignored.
Should allow the onCrash
handler to access the mailbox in some way so that messages are not dropped, at least when on the same system.
Rxjs is quite a heavy dependency and nact doesn't really need it. It is only being used in the persistence layer. The streaming API is most likely only an advantage in pathological cases where hundreds of large events have been persisted.
Steps to Reproduce:
This produces the following error message:
"error: duplicate key value violates unique constraint \"event_journal_uq\"
at Connection.parseE (/Users/nick/root_bank/root-platform/service.insurance.claims/node_modules/pg/lib/connection.js:546:11)
at Connection.parseMessage (/Users/nick/root_bank/root-platform/service.insurance.claims/node_modules/pg/lib/connection.js:371:19)
at Socket.<anonymous> (/Users/nick/root_bank/root-platform/service.insurance.claims/node_modules/pg/lib/connection.js:114:22)
at emitOne (events.js:115:13)
at Socket.emit (events.js:210:7)
at addChunk (_stream_readable.js:266:12)
at readableAddChunk (_stream_readable.js:253:11)
at Socket.Readable.push (_stream_readable.js:211:10)
at TCP.onread (net.js:585:20)"
stateful actor not stopped if null or undefined returned
as https://nact.io/lesson/javascript/stateful-actors mentioned it should shutdown if returned null or undefined, but child continued to receive messages, passed by parent to child.
actor stopped as child
actor still alive
check actor state no null or undefined and stop it
const s = start();
const actor = spawn(s, (state, msg) => { console.log('received', msg); return null; }, 'actor');
dispatch(actor, 'have to exit after this');
dispatch(actor, 'no, still acting');
I want to stop my actors somehow
This is a card description
When compiling a typescript file that calls dispatch
from nact with 3 arguments, I should not receive this error:
actor.ts:39:34 - error TS2554: Expected 2 arguments, but got 3.
39 dispatch(ctx.sender, ctx.name, ctx.self);
I receive the error when compiling:
actor.ts:39:34 - error TS2554: Expected 2 arguments, but got 3.
39 dispatch(ctx.sender, ctx.name, ctx.self);
Update typing to allow for a 3rd optional argument
import {start,dispatch, spawnStateless} from 'nact';
const delay = (time) => new Promise((res) => setTimeout(res, time));
const ping = spawnStateless(system, async (msg, ctx) => {
console.log(msg);
// ping: Pong is a little slow. So I'm giving myself a little handicap :P
await delay(500);
dispatch(ctx.sender, ctx.name, ctx.self);
}, 'ping');
const pong = spawnStateless(system, (msg, ctx) => {
console.log(msg);
dispatch(ctx.sender, ctx.name, ctx.self);
}, 'pong');
dispatch(ping, 'begin', pong);
tsc
I'm trying to use nact with typescript
Stateless actors are as the name implies just that stateless.
Thus it is safe for them to process multiple requests at the same time without blocking.
This will greatly simply using promises in such actors.
Right now nact is using a Weakmap to retrieve the reference to the actor. This is fast, but unsustainable, you cannot do remoting as this this involves copying objects.
Clicking the URL should provide a valid invite
The URL provides an expired invite
Update URL to point to the community page, with the Discord embed
Community outreach
I checked out the branch refactor-typescript
, on which you did some refactoring.
I found you changed many things in nact-core
, but the others that depend on nact-core
haven't been changed properly according to it.
Now it is failing to compile.
I wonder whether you used tsc compiler on your works.
If needed, I want to share with you something about TypeScript development environment and how to run tsc compiler.
And what kind of IDE do you use on development?
I'm using VSCode with some TS extensions, such as TSLint, TS Prettier.
It's good to use some kind of linter and code prettier (formatter).
Thank you.
First step to getting clustering working is to allow actors to communicate with one another via tcp/udp/http/some form of communication channel.
I guess I'd expect spawnPersistent to either throw an error or return the existing child with the same name, rather than just not return.
I was having a problem with not being able to query an actor twice: the first query succeeded, but then further queries timed out. I ended up tracking the problem down to spawnPersistent
not returning when the same name was passed to it a second time.
childActor = spawnUserContactService(ctx.self, userId)
do something like childActor = spawnUserContactService(ctx.self, "userId")
The Hierarchy documentation uses spawn
, but I used spawnPersistent
- I'm guess that's probably not important in this context.
I've started a branch /refactor-typescript
to address these issues.
Problems present on current dev branch are as follows:
sender
property makes sound typing impossible as it either couples the senders and recipients too tightly together or it removes the type information. Sender is to be removed in a soundly typed implemention. c.f. reason-nactActorRef<any>
they should be instead changed to ActorRef<unknown>
to indicate that by default we do not know what messages a child can handle in advance. In general any any should be removed from the public facing api.Json
by default unless they include a encoder/decoder tuple which maps Json to an actual type.Unrelated to soundness, but still important:
get
methods and extraneous fields. These fields and methods need to be removed as references must be serializable to json without information loss.internals
(for want of a better name)dispatch has type signature function dispatch<T>(actor: Ref<T>, msg: T): void;
but the example at https://nact.io/en_uk/lesson/javascript/actor-communication demonstrates passing it a 3rd argument.
It should document itself correctly.
It is missing documentation about the third param.
It's not a great experience to have the docs at your fingertips be incorrect.
Is nact ability to take full advantage of multicore cup resources ?
or all atcors run only on a single cup?
Stateful actors use the "reducer" abstraction to deal with state management (but allow side effects inside the reducer too). It would be a natural addition to allow subscription to state changes (much like redux, but potentially remotely) and if subscriptions are supported, it would be natural to provide this using RXJS as it is highly composable, and a rxjs-like abstraction has been well proven in Akka with Akka streams.
Performance has thus far been a secondary concern to features. As nact evolves and matures however, it will become important to ensure that we have a robust suite of benchmarks so as to evaluate whether performance is improving or regressing.
I just left this for future consideration.
I used pseudo-code here for succinct notation.
interface Event
class ContactCreated(contact: Contact) : Event
class EmailModified(email: string) : Event
interface Command
class CreateContact(form: ContactForm) : Command
class ModifyEmail(contactId: ContactId, email: string) : Command
interface ReadOnlyCommand
class GetContact(contactId: ContactId) extends ReadOnlyCommand
class GetContacts : ReadOnlyCommand
type MSG = Event | Command | ReadOnlyCommand
These are written in TypeScript.
async function handle(state: ST, msg: MSG, ctx: Context): Promise<ST> {
switch (msg.$type) {
// Command
case 'CreateContact': {
const contact = // create new contact
const event = new ContactCreated(contact)
await ctx.persist(event)
return this.handle(state, event, ctx)
}
case 'ModifyEmail': {
const contactId = msg.contactId
const contactActor = ctx.children.get(contactId) || spawnPersistent(ctx.self, ContactActor.handle, {{ persistenceKey }}, {{ name }})
dispatch(contactActor, msg, ctx.sender)
return state
}
// Event
case 'ContactCreated': {
const contactId = msg.contact.contactId
const contactActor = ctx.children.get(contactId)
if (contactActor) {
throw new Error('exception!')
} else {
spawnPersistent(ctx.self, ContactActor.handle, {{ persistenceKey }}, {{ name }}, {
initialState: {
contactId,
contact: msg.contact
}
)
}
return {
...state,
contactIds: [...state.contactIds, contactId]
}
}
// ReadOnlyCommand
case 'GetContact': {
const contactId = msg.contactId
const contactActor = ctx.children.get(contactId) || spawnPersistent(ctx.self, ContactActor.handle, {{ persistenceKey }}, {{ name }})
dispatch(contactActor, msg, ctx.sender)
return state
}
case 'GetContacts': {
// this command doesn't fit well.
// kind of `findAll` command should use `read side` model instead of spawning all actors and retrieving data from them.
}
default:
return state
}
}
interface ST {
contactIds: ContactId[]
}
async function handle(state: ST, msg: MSG, ctx: Context): Promise<ST> {
switch (msg.$type) {
// Command
case 'ModifyEmail': {
const event = new EmailModified(msg.email)
await ctx.persist(event)
return this.handle(state, event, ctx)
}
// Event
case 'EmailModified': {
const nextState = lens<ST>().contact.email.set(msg.email)(state)
return nextState
}
// ReadOnlyCommand
case 'GetContact': {
dispatch(ctx.sender, state.contact)
return state
}
case 'GetContacts': {
dispatch(ctx.sender, state.contact)
return state
}
default:
return state
}
}
interface ST {
contactId: ContactId,
contact: Contact
}
if (!ctx.recovering) { ... }
DoXXX
(imperative)XXXed
(past tense)GetAllXXX
command doesn't fit what nact provides.The current interface allows users to get the children of an actor from the outside. This is bad design because it makes location transparency difficult. The trade off is that this makes writing tests somewhat harder, but I feel that this is worth it.
Both the context object and actor references have a dispatch()
method. The input arguments differ between them. This has lead to confusion (based on feedback).
It perhaps might be worthwhile to refactor the context object to not include the dispatch method. This has the downside of having to explicitly include the sender. On the upside it means a single way of sending a message to other actors and a more consistent api
It'd be an interesting development to get nact working in the browser. There is no fundamental reason right now why it wouldn't be possible. This would enable a few things:
Currently stateful actors just shutdown after a crash. We require a way to define policies on parents to deal with crashes in a sane manner. This is a fundamentally important part of actor systems
This is a problem as it makes it difficult to offer proper flow control
State watch would probably be a more idiomatic way of doing things than using rxjs.
It also would fit into the reason wrapper better.
query
is a somewhat dangerous operation. It can cause a system to lock up if the timeout defaults to what is for most intents and purposes is an infinite duration. The solution is to throw if a timeout is not provided, as this will prevent errors and unexpected behaviour.
Type definitions for TypeScript (index.d.ts) should be provided.
There is no type definition provided, either in nact
or @types/nact
.
https://github.com/civilizeddev/nact/blob/master/lib/index.d.ts
I wrote this above, using it in my own project. I just want to share it.
if you contributors are pleased I think it would be better to rewrite the whole project in TypeScript.
I am also Java and Scala developer, but currently working with TypeScript in Node. I was pleased that there is akka-like actor implementation even in JavaScript. Thank you.
One of the major benefits of actor systems is location transparency; being able to communicate with remote actors in the same way as local actors is an important feature of this
This feature could be as basic as having some means to allow actors to communicate when IP addresses are explicitly provided, or it could also include scheduling (deciding where actors should be put) and clustering.
Another important question is how to ensure that this architecture is flexible enough to support different kinds of transports, while selecting some sane, easy to setup defaults.
ActorContext
type doesn't have ctx.sender
property.
https://github.com/ncthbrt/nact/blob/b28048869c745b305c253d1b22c18c39ba26f122/lib/index.d.ts#L63-L70
but in official docs, when perform response of query
, ctx.sender
is needed.
https://nact.io/en_uk/lesson/javascript/querying
so I did like this(workaround)
// request
await query(actorX, (_: ActorX) => "Let there be light", 5000)
// response(In actorX's msg handler)
dispatch((ctx as any).sender, "and there was light")
const {start, spawnStateless, dispatch} = require('nact');
// create actor system
const system = start();
// a complex calculation model
function intensive_computation() {
let intensity = 100000000;
while (intensity > 0) {
// intensity--;
}
}
// create an actor(actorA)
let actorA = spawnStateless(system,
function (msg, ctx) {
console.log("i am %s, msg: %s", this.name, msg.word);
console.time(this.name);
intensive_computation();
console.timeEnd(this.name);
},
"compute-intensive-actor"
);
// create an actor(actorB)
let actorB = spawnStateless(system,
function (msg, ctx) {
console.log("i am %s, msg: %s", this.name, msg.word);
},
"other-actor"
);
// start actor A
dispatch(actorA, {
word: 'start computing...'
});
// start actor B
dispatch(actorB, {
word: 'i need some cpu resource...'
});
actorB will never be executed before actorA finished. the time-consuming actor will blocks all the other actors.
so... actors in the same system do not run in parallel??
Once spawned, actors take up memory even if they're not handling messages. Shutting actors down frees this resource.
Given nact's design, it would make sense to create a declarative means of specifying when an actor should automatically shutdown due to inactivity. The interface for this could look something like the following:
const { after } = require('nact');
spawn(parent, (state, msg, ctx)=> msg+state, 'name', { shutdown: after(6, 'min').ofInactivity });
Passing an onCrash
property when using spawnStateless()
has no effect: The actor always resumes.
The onCrash
property should be honoured when calling spawnStateless()
.
When calling spawnStateless()
, the onCrash
property is ignored.
This implies that the supervision example in the documentation won't work.
When initialising the properties here, putting the spread of properties
last would fix it, if the intention is to have a (overridable) default:
{ onCrash: (_, __, ctx) => ctx.resume, ...properties }
The supervision example in the documentation won't work, it will always resume (instead of reset
, as documented).
I've just started learning about this package, so I might be missing something.
It would be great if you can tell a little bit more about persisting in the docs, when should we use it and what is the difference between persisting and saving data to db by using ORMs . Thank you.
A number have people have requested example code to better illustrate nact's use cases. I think it's a good idea.
Proposal for sample code:
This framework looks extremely interesting.
I was wondering, have you considered following a "redux-saga" like model for dispatching messages between actors? e.g.:
const pingBehavior = async (msg, ctx) => {
console.log(msg);
await delay(500);
yield dispatch(ctx.sender, ctx.name, ctx.self); // <--- here's where this differs from the docs example.
};
const ping = spawnStateless(system, pingBehavior, 'ping');
Where dispatch()
would just produce an object which is used by the framework to do the actual sending:
const dispatch = (actor, msg, sender) => ({ DISPATCH: { actor, msg, sender }});
This would make actor "behaviour" functions potentially side-effect free, which would have testability benefits.
@ncthbrt
Managed to install nact and used it in a typescript project.
However, i did not manage to run the ping pong example.
dispatch
function in nact/lib/functions.js
with three arguments.ctx
from the callback function in spawnStateless
should able to have sender property.import nact from "nact";
const system = nact.start();
const delay = (time) => new Promise((res) => setTimeout(res, time));
const ping = nact.spawnStateless(system, async (msg, ctx) => {
console.log(msg);
await delay(500);
// ERROR:
// 1. Error message regarding ctx.sender
// Property 'sender' does not exist on type 'ActorContext<any, ActorSystemRef>'.
// 2. Error message regarding nact.dispatch
// (property) self: nact.Ref<any>
// Expected 2 arguments, but got 3.ts(2554)
nact.dispatch(ctx.sender, ctx.name, ctx.self);
}, 'ping');
examples/ping-pong/ping-pong-stateless.js
in typescript fashionrefactor-typescript
branch: https://github.com/ncthbrt/nact/blob/refactor-typescript/nact-core/src/functions.ts
, the dispatch function accepts only two arguments.Are there any steps that I should be aware when using typescript in nact?
Thanks for the help.
What kind of Principle of Use would be there when using nact? Add here.
class
to define Command, Event, Stateclass GetContact {
constructor(contactId) {
this.contactId = contactId
}
}
const command = new GetContact(1234)
const command = {
contactId: 1234
}
interface
is recommended instead of class
interface GetContact {
contactId: number
}
const command: GetContact = {
contactId: 1234
}
{}
is mostly used.constructor
(and prototype
chain) information during the process.The snapshot is taken on the state after the first message, rather than on the null state.
With snapshotEvery: 1 * messages
, I noticed that the snapshot is always one state behind.
This also leads to the error "Failed to take snapshot Error: data should not be null or undefined"
when nact tries to persists the first time.
{snapshotEvery: 1 * messages}
It'd be nice if the snapshot took place after the message is processed rather than before (if that's indeed what's happening). This would ensure that the snapshot is really up-to-date and avoid re-processing the last message every time the actor is restarted.
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.