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; }
}
@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