Search code examples
openrasta

OpenRasta: Can I use a generic handler for versioning resources gracefully?


We have an OpenRasta service, and we'd like to use media types to version our resources. We'll have a different set of DTOs for each new version, distinguishable by namespace. For each version of the DTOs, we have a corresponding media type.

So DTOs will be mapped to media types like this:

Namespace.Dto.V1.MyResource -> application/vnd.Namespace.Dto.V1.MyResource+json    
Namespace.Dto.V2.MyResource -> application/vnd.Namespace.Dto.V2.MyResource+json 

The repository implementation will be specific to the version of the DTOs, but the interface is generic. I would like my handler and codec to be generic as well, so I don't need to copy/paste them for each version of the DTOs. So I want my routes to look like this:

ResourceSpace.Has.ResourcesOfType<V1.MyResource>()
  .AtUri("MyResource/{resourceID}")
  .HandledBy<MyResourceHandler<Dto.V1.MyResource>>()
  .TranscodedBy<MyResourceCodec<Dto.V1.MyResource>>()
  .ForMediaType(mediaTypeMapper.GetMediaType(typeof(Dto.V1.MyResource)));

//V2 of DTOs
ResourceSpace.Has.ResourcesOfType<V2.MyResource>()
  .AtUri("MyResource/{resourceID}")
  .HandledBy<MyResourceHandler<Dto.V2.MyResource>>()
  .TranscodedBy<MyResourceCodec<Dto.V2.MyResource>>()
  .ForMediaType(mediaTypeMapper.GetMediaType(typeof(Dto.V2.MyResource)));

Should this work? Right now, it appears that my service is handling requests with MyResourceHandler<Dto.V1.MyResource> regardless of the Accept header on a GET request, for example.

Any thoughts? We could change our scheme to use URIs like /v1/MyResource/ instead of using the accept header, but it would be great to get this working.

EDIT: I should add that part of the reason we are using media types for versioning is because this is a service for internal use, not meant to be accessible on the public web.


Solution

  • You're registering two resource types on the same URI, only one will get selected, there's no way to do the distinction at request time.

    I don't think versioning in URIs or media types is a good idea on the web. That said, for what you want (different mediatypes), then use the same resource type and use your codec to fill-in the same type from the incoming / outgoing data. That's teh responsibility of a codec in OR, making the junction between a DTO and a media type format.

    On incoming requests, we need to know what resource type you want based on the URI. If you have two different types it ought to be different resources. that said if you do the following that'll work too:

    ResourceSpace.Has.ResourcesNamed("myResource").AtUri("/myResource").HandledBy<ResourceV1Handler>().And.HandledBy<ResourceV2Handler>();
    ResourceSpace.Has.ResourcesOfType<MyV1Resource>().WithoutUri.TranscodedBy<V1Codec>();
    ResourceSpace.Has.ResourcesOfType<MyV2Resource>().WithoutUri.TranscodedBy<V2Codec>();
    

    you can then write

    public class handler {
      public object Post(MyV1Resource resource) {}
      public object Post(MyV2Resource resource) {}
    }
    

    and that'll work. What you won't be able to do is implement the get in that way, as OR assumes one resource type == one URI in most instances.

    As for generics, if you use an IoC container, you can register your handlers in a generic fashion (aka register typeof(IHandler<>) with typeof(Handler<>)). This means any IHandler will get resolved to Handler. You can then simply register HandledBy>() in your registration and you're done. Same applies to codecs (but then again, codecs in OR are there to deal with media type issues, not just as serialization mechanisms, as serialization in itself is evil for the web and should be used seldomly).