C# 推荐的 ServiceStack API 结构

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/15231537/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-10 14:33:09  来源:igfitidea点击:

Recommended ServiceStack API Structure

c#apiservicestack

提问by Tim

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.

我正在尝试找出构建 API 的最佳方式;我们在标准 REST 结构中设置了评论(列出一个、列出所有、创建、更新等)。它不太适合示例的地方是:每个评论都可以链接到一个或多个其他类型,例如事件、位置或事物。

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/

我的想法是 url 应该是这样的:/event/reviews/(或相反的,例如 /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.

然而,我可以看到的问题是每个这些的“GET”应该返回父对象,即一个事件。

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?

那么使用 ServiceStack,处理这种情况的最佳方法是什么?是为每个数据请求创建自定义服务而不是滥用开箱即用的 REST 设置还是我错过了更基本的东西?

采纳答案by mythz

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.

首先,“最佳”解决方案是一个相当主观的术语。我通常会瞄准 DRY、可重用、高性能的解决方案,以促进最少的努力、摩擦和健谈,而其他人可能会根据它遵循 REST 原则的程度来定义“最佳”。因此,根据目标的不同,您会得到不同的回应。我只能提供我将如何处理它。

ServiceStack service implementations are de-coupled from their custom routes

ServiceStack 服务实现与其自定义路由分离

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.

要记住的一件事是,您在 ServiceStack 中定义和设计服务的方式与公开它们的方式相当分离,因为您可以在任何自定义路由下公开您的服务。ServiceStack 鼓励基于消息的设计,因此您应该为每个操作提供不同的消息。

Use a logical / hierarchical Url structure

使用逻辑/分层 URL 结构

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:

我会使用一个逻辑 Url 结构,我的目标是表示一个名词的标识符,它是分层结构的,即父路径对您的资源进行分类并为其提供有意义的上下文。因此,在这种情况下,如果您想公开事件和评论,我倾向于使用以下 url 结构:

/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

这些资源标识符中的每一个都可以应用任何 HTTP 动词

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 EventsServiceand EventReviewsServiceclasses. I should note that I use pluralization for Service Request DTO names myself to avoid clashing with data models of the same name.

基于这些消息,实现应该是相当直接的,我会在 2 个EventsServiceEventReviewsService类中组织这些消息(取决于代码库大小)。我应该注意,我自己对服务请求 DTO 名称使用复数形式,以避免与同名数据模型发生冲突。

Although I've separated UpdateEventand CreateEventhere, I will sometimes will merge them into a single idempotent StoreEventoperation if the use-case permits.

虽然我已经UpdateEventCreateEvent这里分开了,但StoreEvent如果用例允许,我有时会将它们合并为一个幂等操作。

Physical Project Structure

物理项目结构

Ideally the root-level AppHostproject 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.

理想情况下,根级AppHost项目应保持轻量级且无需实现。尽管对于只有少数服务的小型项目,可以将所有内容都放在一个项目中,并在需要时根据需要简单地扩展您的架构。

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.

对于大中型项目,我们推荐以下物理结构,出于本示例的目的,我们假设我们的应用程序称为EventMan

The order of the projects also show its dependencies, e.g. the top-level EventManproject references allsub projects whilst the last EventMan.ServiceModelproject references none:

项目的顺序也显示了它的依赖关系,例如顶级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

With the EventMan.ServiceModelDTO'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 Clientsto provide an end-to-end typed API without any code-gen.

由于EventMan.ServiceModelDTO 保存在它们自己单独的实现和无依赖关系的 dll 中,您可以按原样在任何 .NET 客户端项目中自由共享此 dll - 您可以将其与任何通用C# 服务客户端一起使用以提供结束-to-end 类型的 API,没有任何代码生成。



Update

更新