Рекомендуемая структура API ServiceStack

Я пытаюсь разработать лучший способ структурирования нашего API; у нас есть обзоры, которые мы установили в стандартной структуре REST (список один, список всех, создать, обновить и т.д.). Там, где это не совсем подходит, примеры: каждый обзор может быть связан с одним или несколькими другими типами, например. Событие, местоположение или Вещь.

Мое мышление - это URL-адреса: /event/reviews/ (или наоборот этого, например, /reviews/event/ ) /Местоположение/рецензии/ /Вещь/рецензии/

Однако проблема, которую я вижу, - это "GET", для каждого из которых должен быть возвращен родительский объект, т.е. событие.

Итак, используя ServiceStack, какой лучший способ справиться с этим сценарием? Создать индивидуальную службу для каждого запроса данных, а не злоупотреблять готовой установкой REST или пропустить что-то более фундаментальное?

Ответ 1

Во-первых, "Лучшее" решение - довольно субъективный термин. Я обычно буду стремиться к СУЩНЫМ, многоразовым, эффективным решениям, которые способствуют наименьшим усилиям, трениям и болтливости, в то время как другие могут определить "Лучшее" в том, насколько тесно он следует принципам REST. Таким образом, вы получите разнообразные ответы в зависимости от целей. Я могу только предложить, как я к нему подхожу.

Реализации службы ServiceStack деактивируются с помощью своих настраиваемых маршрутов

Одна вещь, о которой следует помнить, - это то, как вы определяете и разрабатываете свои сервисы в ServiceStack, достаточно деактивированы тем, как вы их раскрываете, поскольку вы можете предоставлять свои услуги по любому настраиваемому маршруту. ServiceStack поощряет дизайн на основе сообщений, поэтому вы должны дать каждой операции отдельное сообщение.

Использовать логическую/иерархическую структуру Url

Я бы использовал логическую структуру Url, которую я хотел бы представлять идентификатор существительного, который является иерархически структурированным, то есть родительский путь классифицирует ваш ресурс и дает ему осмысленный контекст. Поэтому в этом случае, если вы хотите разоблачить события и просмотрите мою склонность идти со следующей структурой URL:

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

Каждый из этих идентификаторов ресурсов может иметь любой HTTP-глагол, применяемый к ним

Реализация

Для реализации я обычно следую за дизайном на основе сообщений и группирую все связанные операции на основе типа ответа и контекста вызова. Для этого я бы сделал что-то вроде:

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

И следуйте аналогичной схеме для обзоров событий

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

Реализация должна быть достаточно простой на основе этих сообщений, которые (в зависимости от размера базы кода) я бы организовал в классах < EventsService и EventReviewsService. Я должен отметить, что я использую плюрализацию для имен DTO Service Request, чтобы избежать столкновения с моделями данных с тем же именем.

Хотя я разделял UpdateEvent и CreateEvent здесь, я иногда буду объединять их в одну операцию idempotent StoreEvent, если разрешает прецедент.

Физическая структура проекта

В идеале, проект AppHost на уровне корня должен быть легким и свободным от реализации. Хотя для небольших проектов, в которых есть только несколько сервисов, все в порядке, чтобы все было в одном проекте и просто увеличивало вашу архитектуру по мере необходимости.

Для проектов средней и большой мы рекомендуем физическую структуру, ниже которой для целей этого примера мы предположим, что наше приложение называется EventMan.

Порядок проектов также показывает свои зависимости, например. проект EventMan верхнего уровня ссылается на <проекты > все, тогда как последний проект EventMan.ServiceModel ссылается на 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

С EventMan.ServiceModel DTO, хранящимся в отдельной отдельной DLL-версии и без зависимостей, вы можете свободно делиться этой DLL в любом .NET-проекте клиента as-is, который вы можете использовать с любым из общих С# Service Clients, чтобы обеспечить сквозной типизированный API без кода-gen.


Update