Orleankka icon indicating copy to clipboard operation
Orleankka copied to clipboard

[EXAMPLE] Http-endpoint with WebAPI (C#)

Open yevhen opened this issue 9 years ago • 8 comments

Showcase how to dealing with serialization, routing, media types, versioning, etc. See @damianh Cedar.CommandHandling for inspiration

yevhen avatar Jun 29 '15 16:06 yevhen

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.

  1. The behavior of a read command. Should errors be saved? if yes, should they be overridden by the second/third attempt?
  2. Implementation details. Each command could be a single grain or an Orleannkka middleware

aprooks avatar Oct 24 '18 16:10 aprooks

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}

yevhen avatar Oct 24 '18 19:10 yevhen

So you don't build an inderection layer, but send messages straight to grains if they pass validation?

Do you distinguish queries/commands?

aprooks avatar Oct 24 '18 20:10 aprooks

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

yevhen avatar Oct 24 '18 21:10 yevhen

This is just an internal endpoint, of course. We don’t expose actors over http as is.

yevhen avatar Oct 24 '18 21:10 yevhen

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 :)

aprooks avatar Oct 24 '18 21:10 aprooks

Haha, we actually did exactly that in our web api project))

yevhen avatar Oct 24 '18 21:10 yevhen

Maybe Brighter could be used also.

jpiquot avatar May 11 '19 06:05 jpiquot