Skip to content

Instantly share code, notes, and snippets.

@phatboyg
Created November 28, 2011 22:59
Show Gist options
  • Save phatboyg/1402493 to your computer and use it in GitHub Desktop.
Save phatboyg/1402493 to your computer and use it in GitHub Desktop.
Example of applying behavior to an actor
class Cleaner_version_of_defining_actor_behavior
{
public void Some_method_in_your_code()
{
// create the actor instance, passing the state and an initializer
// in this case, applying the initial behavior of to the actor
ActorRef agent = Actor.New<MyState>(x => x.Apply<DefaultBehavior>());
// now messages can be sent to the actor to make it do things
// we will use a stateless actor for that interaction
StatelessActor.New(actor =>
{
agent.Request(new A(), actor)
.ReceiveResponse<B>(response => { });
});
}
// the state of an actor can be any type, and is accessible by the
// behaviors applied to the actor
class MyState
{
public string SomeValue { get; set; }
}
class DefaultBehavior :
Behavior<MyState>
{
readonly Actor<MyState> _actor;
// the actor is passed to the behavior, which gives it access
// to state. A new instance of the behavior class is created when
// behavior is applies, and this executes within the actor execution
// context
public DefaultBehavior(Actor<MyState> actor)
{
_actor = actor;
}
// by a PublicMessageMethodConvention, this methods gets wired up
// to the inbox to deliver messages of type A as part of the behavior
public void Handle(Message<A> message)
{
string lastValue = _actor.State.SomeValue;
_actor.State.SomeValue = message.Body.Value;
message.Respond(new B
{
LastValue = lastValue
});
// after this message, we want to keep this behavior current so we
// can handle more messages of type A
_actor.ReapplyBehavior();
}
// receiving a disable message will change the behavior of the actor
public void Handle(Message<Disable> message)
{
_actor.Apply<DisabledBehavior>();
}
}
// another behavior defined for the actor
class DisabledBehavior :
Behavior<MyState>
{
readonly Actor<MyState> _actor;
public DisabledBehavior(Actor<MyState> actor)
{
_actor = actor;
}
// in this case, handling the enable message changes back to the default
// behavior
public void Handle(Message<Enable> message)
{
_actor.Apply<DefaultBehavior>();
}
}
class A
{
public string Value { get; set; }
}
class B
{
public string LastValue { get; set; }
}
class Disable
{
}
class Enable
{
}
}
class Cleaner_version_of_defining_actor_behavior
{
public void Some_method_in_your_code()
{
// create the actor instance, passing the state and an initializer
// in this case, applying the initial behavior of to the actor
ActorRef agent = Actor.New<AgentState>(x => x.Apply<DefaultAgentBehavior>());
// now messages can be sent to the actor to make it do things
// we will use a stateless actor for that interaction
StatelessActor.New(actor =>
{
agent.Request(new RequestStockQuote {Symbol = "AAPL"}, actor)
.ReceiveResponse<StockQuote>(response => { });
});
}
// the state of an actor can be any type, and is accessible by the
// behaviors applied to the actor
class DefaultAgentBehavior :
Behavior<AgentState>
{
readonly Actor<AgentState> _actor;
// the actor is passed to the behavior, which gives it access
// to state. A new instance of the behavior class is created when
// behavior is applies, and this executes within the actor execution
// context
public DefaultAgentBehavior(Actor<AgentState> actor)
{
_actor = actor;
}
// by a PublicMessageMethodConvention, this methods gets wired up
// to the inbox to deliver messages of type A as part of the behavior
public void Handle(Message<RequestStockQuote> message)
{
_actor.State.RequestCount++;
var requestActor = Actor.New<RequestState>(x => x.Apply<InitialRequestBehavior>());
requestActor.Request(message.Body, _actor)
.ReceiveResponse<StockQuote>(quoteResponse =>
{
_actor.State.LastQuote[message.Body.Symbol] = quoteResponse.Body;
message.Respond(quoteResponse.Body);
});
}
// receiving a disable message will change the behavior of the actor
public void Handle(Message<UseCache> message)
{
_actor.Apply<CacheAgentBehavior>();
}
}
// another behavior defined for the actor
class CacheAgentBehavior :
Behavior<AgentState>
{
readonly Actor<AgentState> _actor;
public CacheAgentBehavior(Actor<AgentState> actor)
{
_actor = actor;
}
public void Handle(Message<RequestStockQuote> message)
{
_actor.State.RequestCount++;
bool handled = _actor.State.LastQuote.WithValue(message.Body.Symbol, quote =>
{
_actor.State.CachedRequestCount++;
message.Respond(quote);
});
if(!handled)
{
// we may want to send a temporary unavailable response or something
}
}
// in this case, handling the enable message changes back to the default
// behavior
public void Handle(Message<UseLive> message)
{
_actor.Apply<DefaultAgentBehavior>();
}
}
class AgentState
{
public AgentState()
{
LastQuote = new DictionaryCache<string, StockQuote>();
}
public int RequestCount { get; set; }
public int CachedRequestCount { get; set; }
public Cache<string, StockQuote> LastQuote { get; set; }
}
class RequestState
{
public string Symbol { get; set; }
}
class InitialRequestBehavior :
Behavior<RequestState>
{
readonly Actor<RequestState> _actor;
readonly ISessionFactory _sessionFactory;
public InitialRequestBehavior(Actor<RequestState> actor, ISessionFactory sessionFactory)
{
_actor = actor;
_sessionFactory = sessionFactory;
}
public void Handle(Message<RequestStockQuote> message)
{
_actor.State.Symbol = message.Body.Symbol;
// we do some expensive work to get the quote value
using(var session = _sessionFactory.OpenSession())
{
var stock = session.Load<Stock>(message.Body.Symbol);
message.Respond(new StockQuote
{
LastPrice = stock.LastPrice
});
}
}
}
class RequestStockQuote
{
public string Symbol { get; set; }
}
class StockQuote
{
public decimal LastPrice { get; set; }
}
class UseCache
{
}
class UseLive
{
}
class Stock
{
public decimal LastPrice { get; set; }
}
@andlju
Copy link

andlju commented Nov 29, 2011

I really like the way this is heading, looks way easier to work with!

One thing that I'm not quite sure about is that you create the state before passing it to the actor (line 6-10). That makes it possible to access it outside of the actor context which seems a bit dangerous. I'd probably prefer something like:

 ActorRef agent = Actor.New<MyState>(x => x.Apply<DefaultBehavior>());

@haf
Copy link

haf commented Nov 29, 2011

What would the loop/recurse/continue code look like?

@phatboyg
Copy link
Author

@andlju:
Thanks. As to the state, I could do a New() as you suggested, and it crossed my mind to do that very thing as well. I'd have to ensure that I could new it up (by adding a new() constraint to the type), but would also allow the state factory to be overloaded through configuration of the actor (which I didn't know, since it's not mandatory and only used to extend the behavior model). I guess it goes back to creating sharp tools that might let you cut yourself vs. hiding everything in the API creating limitations. But that's good feedback, and I think adding a default constructor of state for new actors is a good idea.

Oh, the other benefit of allowing it to be passed in is to setup some initial values for the actor (such as some type of identifier, key, etc.). Also, the state can be an interface so that the actor behavior could be applied to a class that implements the state interface, allowing some pretty interesting applications of the actor model on top of existing models.

@phatboyg
Copy link
Author

@haf:
The initial thought would be the behavior would remain in place unless changed. I'll add a change to the example above, but the gist of it is that you would call:

_actor.ChangeTo<AnotherBehavior>();

This would remove the current behavior and replace it with the newly specified behavior. If an actor didn't change behavior, it would continue to support the current behavior.

Did you have something in mind or is this inline with what you were thinking?

@haf
Copy link

haf commented Nov 29, 2011

@phatboyg:

I like the new initialization syntax. Rather an overload if we need what type is before the current GetActor() call, and stateless actor is good too.

So behaviours would be mutually exclusive? Change it and there's no overlapping?

I'm curious; what would a functional interface look like for defining alterations of what message handling function is used to handle the message?

loop inbox ... =
  _actor $ changeTo ignoringRequests
  loop inbox ...

ignoringRequests :: ActorRef a -> Message A -> Hash -> Hash
ignoringRequests _actor msg state =
  state
  (* etc *)

servicingRequests _actor msg state =
  actor $ reply (lookup state "lastValue")
  put state "lastValue" (msg $ value)

Thinking of what it would be like porting this to an F# API.

Another thought: What if you made a DSL for a petri-net as a part of your state machine project which would be adapted to suit the actor model.

The language/DSL would be an extension to the state syntax declaration syntax already existing: it would buffer messages into the nodes/places of the net, let the actor have messages or tuples thereof during the arcs when all required places are sated/fulfilled, and let actors change behaviours during transitions (between behaviours in this case).

In this case you could out-of-the-box use graph theory to analyze your actors, rather than having to do execution or a lot of reflection...?

The arcs would be the invocation of your function: from actor instance to message to unit?

@phatboyg
Copy link
Author

@haf:

Behaviors are mutually exclusive at this point, mostly to avoid confusion. You can, however, use inheritance (since behaviors are classes) to implement some base behavior and then subclass for each more specific behavior. I would also like to be able to Apply/Compose multiple behaviors, but the concern is what happens when there are conflicts (multiple handlers for the same message, etc.). I'm still thinking about multiple active behaviors, but I haven't implemented it yet.

I added an example of altering behavior in response to a message to the Gist.

This DSL is really for C#. The F# syntax is so different that a different API likes makes sense there.

The parallels between state machines, petri nets, and behaviors/transitions are so rich that it's likely a common base-level engine could be extracted to support multiple definition types. In fact, the behavior DSL is based on the Automatonymous DSL I created for state machines. I'm trying to avoid over-abstraction at this point since it doesn't serve any purpose at this point.

And yes, I do see being able to reflect across the entirely of an actors code base (using Mono.Cecil) to create a graph of the entire behavior of an actor. :)

@haf
Copy link

haf commented Nov 29, 2011

That sounds cool and plus, it's OSS so the only possible way forward is improvement - right? ;)

@andlju
Copy link

andlju commented Nov 29, 2011

I think I'd prefer that the behavior stays unless you tell it not to. The need to explicitly state that you wanted the actor to continue was one of the things I felt would bug me with the old syntax. Or is there a reason why you wouldn't want that as the default?

@haf
Copy link

haf commented Nov 29, 2011

@andlju: often actors (such as in erlang) are recursive constructs similar to function evaluations, only with the quirk that they are evaluating on fibers. As such, it's the normal rather than the exception to have to explicitly say to recurse/continue. The discussion above, however, isn't about this continuation/recursion, but about behaviour - that is to say - we're not discussing the behaviour of continuing or not, but rather changing behaviour at runtime as a result to state or events/messages being received. What I'm then saying is that the reflection-way of doing it is provable undecidable, while a dsl possibly could be provable (decidable, but we're not evaluating anything but the built graph). That is not saying however, that reflection wouldn't possibly work in some cases.

At least that is my understanding...

@andlju
Copy link

andlju commented Nov 29, 2011

@haf: Ahh, yes. I see. I'm sort of confusing two different things.

I've really forgotten everything I once knew about Erlang.. should probably go try it out again..!

@phatboyg
Copy link
Author

@andlju:
I agree that behavior should stay active unless explicitly changed. That alone encourages the fine-grained actor model that is not obvious. Many times, if you have to do something to process a request, you should be doing that with a child actor, and then coordinate the response to the originator. I need to work up a good example of how this is done and split up into many smaller actors to manage the state, the response actor, and various bits.

@haf:
With Stact, the recursive nature and explicit continuation is something I consider an impediment to actor model adoption, and one I'm try to eliminate. I hope to make it easier for people to understand how actors can help decompose a service/system/agent into smaller bits that act independently.

Both are great discussion, the kind I was hoping to gain from posting this small example.

@phatboyg
Copy link
Author

A more complex example added below the original, this one does a few more things including spawning child actors to handle the requests and a more obvious reason to change behavior (to use a cache when a service is unavailable in this case).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment