Orleankka
Orleankka copied to clipboard
[EXAMPLE] Http-endpoint with WebAPI (C#)
Showcase how to dealing with serialization, routing, media types, versioning, etc. See @damianh Cedar.CommandHandling for inspiration
I explored a bit Cedar and had some idea on how to implement this.
First I don't like an idea of having a single URL for commands like POST /api/commands
so I would go with something like
PUT /api/{targetGrainType}/{grainId}/actions/{commandType}/{commandId}
or it could be
POST /api/{targetGrainType}/{grainId}/actions/
but with a required header of what kind of command is it and if no id provided it would redirect to newly generated command id via 307 response (like https://eventstore.org/docs/http-api/creating-writing-a-stream/index.html)
graintype/commandTypes should not be exactly .net types, but could be annotations or a routing table.
this way it should be easier to have 1 to 1 matching of a URI to actor path and HTTP body to actor message.
Some things which are fuzzy for me.
- The behavior of a read command. Should errors be saved? if yes, should they be overridden by the second/third attempt?
- Implementation details. Each command could be a single grain or an Orleannkka middleware
From my experience, the simplest way to implement such a generic RPC endpoint would be tunneling.
POST /api/{grainInterface}/{grainId}/{grainMethod}
BODY
{
"param1": "foo",
"param2": "bar"
}
This is easy to bind with Orleans's native method-based interfaces but in our case {grainMethod}
should be specifying message type. Something like:
POST /api/MyApp.MyNamespace.InventoryItem/IT-1234/MyApp.MyNamespace.DeactivateInventoryItem
BODY
{
"param1": "foo",
"param2": "bar"
}
This would require some kind of pre-scanning or pre-registering of all known message types. We use special contract binder and mark all of our custom message classes with special attribute (ie [BindingContract("MentionStream.Projections.StartReplicating")]
). That gives us a layer of indirection (we bind routes to contract ids) and a simple way to find and register all of the messages by scanning the assembly. We use strongly-typed actors and ActorMessage<TActor, TResult>
almost everywhere and that enables some preliminary validation on the side of http api .
P.S. The above could be complemented with GET
endpoint, which is just a simplified way of invoking POST
for methods (messages) that don't have any required fields to fill in.
GET /api/{grainInterface}/{grainId}/{grainMethod}
So you don't build an inderection layer, but send messages straight to grains if they pass validation?
Do you distinguish queries/commands?
In tunneling (REST0), cmd/queries are not distinguished. It’s all POST with result. It maps to always send messages to actors via Ask.
Yes, I do straightforward mapping between routes and actors/messages and then simply use Ask to invoke
This is just an internal endpoint, of course. We don’t expose actors over http as is.
it makes more sense then. I was thinking of building something like https://servicestack.net/ routing with security/validation middlewares.
Should be a nice exercise at least :)
Haha, we actually did exactly that in our web api project))
Maybe Brighter could be used also.