67

I'm trying work out the best way to structure our API; we have Reviews which we've setup in a standard REST structure (list one, list all, create, update etc). Where it doesn't quite fit the examples is: each review can be linked to one or more other types e.g. Event, Location or Thing.

My thinking is the urls would be along the lines of: /event/reviews/ (or the reverse of this e.g. /reviews/event/) /location/reviews/ /thing/reviews/

The issue I can see however is the "GET" for each of these should return the parent object i.e. an Event.

So using ServiceStack, what's the best way to handle this scenario? Is it to create a custom service for each data request rather than abusing the out-of-the-box REST setup or have I missed something more fundamental?

Tim
  • 5,443
  • 2
  • 22
  • 26

1 Answers1

140

Firstly "Best" solution is a fairly subjective term. I'll generally aim for DRY, re-usable, performant solutions that promotes the least effort, friction and chattiness, whilst others may define "Best" in how closely it follows the principles of REST. So you will get varied responses depending on what the goals are. I can only offer how I would approach it.

ServiceStack service implementations are de-coupled from their custom routes

One thing to keep in mind is how you define and design your services in ServiceStack are fairly de-coupled in how you expose them, since you can expose your services under any custom route. ServiceStack encourages a message-based design so you should give each operation a distinct message.

Use a logical / hierarchical Url structure

I'd use a logical Url structure that I aim to represent the identifier of a noun, which is hierarchically structured, i.e. the parent path categorizes your resource and gives it meaningful context. So in this case if you wanted to expose Events and reviews my inclination is to go with following url structure:

/events             //all events
/events/1           //event #1
/events/1/reviews   //event #1 reviews

Each of these resource identifiers can have any HTTP Verb applied to them

Implementation

For the implementation I generally follow a message-based design and group all related operations based on Response type and call context. For this I would do something like:

[Route("/events", "GET")]
[Route("/events/category/{Category}", "GET")] //*Optional top-level views
public class SearchEvents : IReturn<SearchEventsResponse>
{
   //Optional resultset filters, e.g. ?Category=Tech&Query=servicestack
   public string Category { get; set; } 
   public string Query { get; set; }
}

[Route("/events", "POST")]
public class CreateEvent : IReturn<Event>
{
   public string Name { get; set; }
   public DateTime StartDate { get; set; }
}

[Route("/events/{Id}", "GET")]
[Route("/events/code/{EventCode}", "GET")] //*Optional
public class GetEvent : IReturn<Event>
{
   public int Id { get; set; }
   public string EventCode { get; set; } //Alternative way to fetch an Event
}

[Route("/events/{Id}", "PUT")]
public class UpdateEvent : IReturn<Event>
{
   public int Id { get; set; }
   public string Name { get; set; }
   public DateTime StartDate { get; set; }
}

And follow a similar pattern for Event reviews

[Route("/events/{EventId}/reviews", "GET")]
public class GetEventReviews : IReturn<GetEventReviewsResponse>
{
   public int EventId { get; set; }
}

[Route("/events/{EventId}/reviews/{Id}", "GET")]
public class GetEventReview : IReturn<EventReview>
{
   public int EventId { get; set; }
   public int Id { get; set; }
}

[Route("/events/{EventId}/reviews", "POST")]
public class CreateEventReview : IReturn<EventReview>
{
   public int EventId { get; set; }
   public string Comments { get; set; }
}

The implementation should be fairly straight forward based on these messages, which (depending on code-base size) I would organize in 2 EventsService and EventReviewsService classes. I should note that I use pluralization for Service Request DTO names myself to avoid clashing with data models of the same name.

Although I've separated UpdateEvent and CreateEvent here, I will sometimes will merge them into a single idempotent StoreEvent operation if the use-case permits.

Physical Project Structure

Ideally the root-level AppHost project should be kept lightweight and implementation-free. Although for small projects with only a few services it's ok for everything to be in a single project and to simply grow your architecture when and as needed.

For medium-to-large projects we recommend the physical structure below which for the purposes of this example we'll assume our Application is called EventMan.

The order of the projects also show its dependencies, e.g. the top-level EventMan project references all sub projects whilst the last EventMan.ServiceModel project references none:

- EventMan
    AppHost.cs              // ServiceStack ASP.NET Web or Console Host Project

- EventMan.ServiceInterface // Service implementations (akin to MVC Controllers)
    EventsService.cs
    EventsReviewsService.cs

- EventMan.Logic            //For larger projs: pure C# logic, data models, etc
    IGoogleCalendarGateway  //E.g of a external dependency this project could use

- EventMan.ServiceModel     //Service Request/Response DTOs and DTO types
    Events.cs               //SearchEvents, CreateEvent, GetEvent DTOs 
    EventReviews.cs         //GetEventReviews, CreateEventReview
    Types/
      Event.cs              //Event type
      EventReview.cs        //EventReview type

With the EventMan.ServiceModel DTO's kept in their own separate implementation and dependency-free dll, you're freely able to share this dll in any .NET client project as-is - which you can use with any of the generic C# Service Clients to provide an end-to-end typed API without any code-gen.


Update

Community
  • 1
  • 1
mythz
  • 141,670
  • 29
  • 246
  • 390
  • 1
    That's very useful thank you Demis it has certainly cleared things up for me. I agree "Best" was the wrong term to use and personally also prefer the DRY route. – Tim Mar 07 '13 at 10:01
  • 1
    @mythz What about fluent validation classes if we want to use ValidationFeature ? I am thinking to put them in ServiceInterface Layer to keep ServiceModel layer dependecy free as possible. – mustafasturan Apr 12 '13 at 08:45
  • 1
    To persist the model to an external DB, would you do that in the Logic layer or the ServiceInterface layer? – robrtc Apr 29 '13 at 08:38
  • 2
    @robrtc yeah either, they both contain impl logic, the Logic project is for large solutions where you would keep sharable logic like a repository if you had one. But I'd still also have adhoc db access in services that aren't shared/needed anywhere else. – mythz Apr 29 '13 at 11:03
  • 2
    @mythz I've read it's a good idea to start with a 'version 1' folder structure for your api in case you make it public...so here that would become... /api/v1/events/ ...what are your thoughts on that and what would be the best way to incorporate that with your recommendations? – Darren May 05 '13 at 06:15
  • Very good post - thank you. Repeating Name and StartDate in CreateEvent and UpdateEvent classes doesn't seem very DRY to me, and I'm assuming it's because you have simplified your example, and each should contain an Event object instead. However, fundamentally the Create and Update methods differ by the fact that Update has an ID and Create does not - How do you suggest you deal with this? – Tom Wells Jun 12 '13 at 12:12
  • 1
    I see my question above is asked/answered here: http://stackoverflow.com/a/12413091/329367 – Darren Jun 22 '13 at 22:07
  • 1
    What do you mean by dependency-free dll ServiceModel has a dependency on servicestack correct? – Aaron Fischer Jun 27 '13 at 19:47
  • 2
    @AaronFischer If you're annotating DTO's with Attributes then it only needs a dep on `ServiceStack.Interfaces.dll` which is an impl-free .dll. At the moment SS.Interfaces still in the SS.Common NuGet pkg, in the next NuGet re-factor it will be in its own fine-grained pkg. Tho it doesn't matter in practice since .NET clients need `SS.Common` in order to use the typed .NET clients. – mythz Jun 27 '13 at 19:52
  • 5
    @mythz This answer has become a popular general resource for servicestack api design. However the request message classes are missing the `IReturn<>` markers which I believe are still the recommended approach? Would you mind adding them to your answer to make it more comprehensive? It helps to clarify the response message design choice between `[RequestName]Response` wrapper messages vs literal `List` messages. – Tyson Jun 30 '13 at 03:05
  • 1
    Thanks for this answer! Can you explain why you didn't name the `Events`/`EventReviews` Request-DTOs `GetEvents`/`GetEventReviews`? Wouldn't that be more consistent? – M4N Jul 25 '13 at 11:37
  • And another question: you didn't use the `IReturns<>` interface on the Request-DTOs. Is this, because it is only relevant when using managed/strongly-typed clients? – M4N Jul 25 '13 at 11:39
  • 2
    @mythz Are the classes in the "Types" folder the response types? If so, does that mean the properties are duplicated across the response types and the root-level classes in your ServiceModel project? If these aren't the response types, how are they used by the service projects? I'm mainly trying to determine what the purpose of the "Types" folder is besides containing DTO's for use by the client. – Bob Wintemberg Aug 03 '13 at 23:15
  • why add the routes this way, it makes it totally a maintenance nightmare. Why not use a route table, one location to define and manage routes? – PositiveGuy Sep 22 '13 at 05:06
  • and is there any example solutions that show an example of the recommended structure above that we can take a look at? – PositiveGuy Sep 22 '13 at 05:11
  • Where lives IDbConnectionFactory interfaces/classes ? – labilbe Oct 23 '15 at 14:23
  • @labilbe in [ServiceStack.Common](https://github.com/ServiceStack/ServiceStack/blob/master/src/ServiceStack.Common/Data/IDbConnectionFactory.cs), a good way to find classes is using the `T` shortcut when viewing the GitHub repo. – mythz Oct 23 '15 at 14:26
  • @mythz sorry, I mean, when you develop your own solution using ServiceStack. If you create a IMyDbConnectionFactory : IDbConnectionFactory. Where is the place of IMyDbConnectionFactory? (ServiceModel or ServiceInterface) – labilbe Oct 23 '15 at 14:27
  • 1
    @labilbe Only DTO's or types/metadata relating to the external service contract [should go in ServiceModel](http://stackoverflow.com/a/32940275/85785). ServiceInterface contains classes needed for the Services impl. But I'd normally never subclass IDbConnectionFactory, the only use-case I've seen is for supporting [MultiTenantDbFactory](https://github.com/ServiceStack/ServiceStack/blob/master/tests/ServiceStack.WebHost.Endpoints.Tests/MultiTennantAppHostTests.cs#L53). – mythz Oct 23 '15 at 14:38
  • @mythz I don't know if I am doing it well, so I created a gist https://gist.github.com/labilbe/8653bb53383478870546 to show you – labilbe Oct 25 '15 at 11:55
  • @mythz Does this still apply to .net core projects using ServiceStack? – JMK Apr 02 '17 at 14:14
  • I think the nomenclature of 'Interface' is confusing if its really the implementation. I use 'ServiceModel' and 'ServiceImplementation' – kevinc May 25 '17 at 01:44
  • @kevinc It's the nomenclature used for the Service pattern, it represents the Gateway/[Service Interface](https://msdn.microsoft.com/en-us/library/ff647559.aspx) for your Services. – mythz May 25 '17 at 01:52
  • A Customer with only 1 attribute (Name) is not a real-world example. – Mike W Dec 28 '20 at 18:57