REST-API模型的最佳实践

时间:2021-08-28 21:14:31

I am working on a REST-API and have run into an architectural problem.

我正在研究REST-API并遇到了架构问题。

The model 'Book' represents a single book with properties and CRUD-based functions. It loads itself from a database via a read function.

模型“Book”表示具有属性和基于CRUD的函数的单本书。它通过读取功能从数据库加载自身。

However, what if I want to get all books in the database? The current book model does not cover this use case.

但是,如果我想获取数据库中的所有书籍怎么办?目前的书籍模型不包括这个用例。

I have tried several approaches:

我尝试了几种方法:

1.) A second model called 'Books'. It has a read function which returns a list of book objects.

1.)第二个模型叫做“书籍”。它有一个read函数,返回一个book对象列表。

2.) The model 'Book' itself has a readAll function which loads all books.

2.)模型'Book'本身有一个readAll函数,可以加载所有书籍。

3.) The model 'Book' is non-functional, it only has properties. Instead a 'BookStorage' class loads the data and fills one/multiple models.

3.)模型'Book'不起作用,它只有属性。相反,'BookStorage'类加载数据并填充一个/多个模型。

I am not satisfied with any of these approaches. Is there a best practice for this scenario?

我对这些方法都不满意。这种情况有最佳实践吗?

4 个解决方案

#1


1.) A second model called 'Books'. It has a read function which returns a list of book objects.

1.)第二个模型叫做“书籍”。它有一个read函数,返回一个book对象列表。

This is okay, but users and future developers on the project may be unclear on the fact that you have both Book and Books. So be sure to document the API, and comment the code. Also, you do need to consider input filters to limit down the results, or at least a method to page results. Google once estimated there were 130 million books, so you want to get all of them at once?

这没关系,但项目的用户和未来的开发人员可能不清楚你有Book和Books这两个事实。所以一定要记录API,并注释代码。此外,您需要考虑输入过滤器来限制结果,或者至少考虑一种页面结果的方法。谷歌曾经估计有1.3亿本书,所以你想立刻获得所有书籍?

e.g. SERVER/Books/?skipRecords=0&limit=100

2.) The model 'Book' itself has a readAll function which loads all books.

2.)模型'Book'本身有一个readAll函数,可以加载所有书籍。

This is not ideal as it violates the single responsibility principal. Mostly it just doesn't make a lot of sense in an OO situation to have a single book entity be able to list all of it's sibling entities.

这并不理想,因为它违反了单一的责任主体。大多数情况下,在OO情况下,单个图书实体能够列出其所有兄弟实体并不是很有意义。

e.g. SERVER/TheHobbit/readAll.... yuck.

例如SERVER / TheHobbit / readAll ....哎呀。

3.) The model 'Book' is non-functional, it only has properties. Instead a 'BookStorage' class loads the data and fills one/multiple models. If you can expose these functional extension via the API, then that is an fine solution as well. It all comes down to documentation.

3.)模型'Book'不起作用,它只有属性。相反,'BookStorage'类加载数据并填充一个/多个模型。如果您可以通过API公开这些功能扩展,那么这也是一个很好的解决方案。这一切都归结为文档。

Maybe it ends up looking like this

也许最终看起来像这样

e.g.

SERVER/BookStorage/GetAllBooks?skipRecords=0&limit=100

SERVER/BookStorage/GetBook?title=TheHobbit

#2


3.) The model 'Book' is non-functional, it only has properties. Instead a 'BookStorage' class loads the data and fills one/multiple models.

3.)模型'Book'不起作用,它只有属性。相反,'BookStorage'类加载数据并填充一个/多个模型。

This approach is similar to the Repository Pattern and is very common. The BookStorage would be a BookRepository with an interface like:

这种方法类似于存储库模式,非常常见。 BookStorage将是一个BookRepository,其界面如下:

public interface IBookRepository
{
    Book GetById(int id);
    IEnumerable<Book> GetAll();
    // Other methods for creating, updating and deleting books.
}

In your controller you would then have:

在您的控制器中,您将拥有:

public class BooksController: ApiController
{
    private readonly IBookRepository _bookRepository;

    public BooksController(IBookRepository bookRepository)
    {
        _bookRepository = bookRepository;
    }

    [Route("api/books/{id}")]
    public IHttpActionResult Get(int id)
    {
        var book = _bookRepository.GetById(id);

        // ...
    }

    [Route("api/books")]
    public IHttpActionResult Get()
    {
        var books = _bookRepository.GetAll();

        // ...
    }
}

If you want paging, you would add a filter like Get(int page = 0) and then depending on your page size, use something like bookRepository.GetAll().Skip(PAGE_SIZE * page).Take(PAGE_SIZE).

如果你想要分页,你可以添加一个像Get(int page = 0)这样的过滤器,然后根据你的页面大小,使用像bookRepository.GetAll()这样的东西。跳过(PAGE_SIZE *页面).Take(PAGE_SIZE)。

A model should not load itself as it would violate the Single responsibility principle.

模型不应该加载自身,因为它违反了单一责任原则。

#3


A great resource when designing RESTful webservices is Martin Fowler's article Richardson Maturity Model.

设计RESTful Web服务时的一个很好的资源是Martin Fowler的文章Richardson Maturity Model。

To summarise for your book case:

总结您的书籍案例:

  • use POST /book to create a book
  • 使用POST / book来创建一本书

  • use PUT /book to update a book
  • 使用PUT / book来更新书籍

  • use GET /book?author=Shakespeare&year=1602&someOtherParam=someValue to find books
  • 使用GET / book?author = Shakespeare&year = 1602&someOtherParam = someValue查找书籍

  • use GET /book/:id to retrieve the details of a particular book
  • 使用GET / book /:id来检索特定书籍的详细信息

  • use DELETE /book/:id to delete a certain book
  • 使用DELETE / book /:id删除某本书

Also you might want to follow the HATEOAS principles, so you'll need to include all relevant links for a book into a links property, so that clients of your API will not need to build their own link when they'll want to add/edit/find/delete a book.

此外,您可能希望遵循HATEOAS原则,因此您需要将书籍的所有相关链接包含在链接属性中,以便API的客户在他们想要添加/时不需要构建自己的链接编辑/查找/删除一本书。

Although simple at first sight, designing a good RESTful webservice is not that easy, however HATEOAS helps because url schema changes on the server side don't affect clients as they don't hardcode the URL's needed for the CRUD operations. All you need to do is provide a starting point, e.g. a base url where all contents can be discovered, and clients can start from there navigating through your webservice.

虽然初看起来很简单,但设计一个好的RESTful Web服务并不容易,但HATEOAS会有所帮助,因为服务器端的url架构更改不会影响客户端,因为它们不会对CRUD操作所需的URL进行硬编码。您需要做的就是提供一个起点,例如:一个基本URL,可以发现所有内容,客户端可以从那里开始浏览您的Web服务。

#4


Read the CRUD Operations section here. You will find REST best practices to be followed when designing a REST API

请阅读此处的CRUD操作部分。您将在设计REST API时找到要遵循的REST最佳实践

#1


1.) A second model called 'Books'. It has a read function which returns a list of book objects.

1.)第二个模型叫做“书籍”。它有一个read函数,返回一个book对象列表。

This is okay, but users and future developers on the project may be unclear on the fact that you have both Book and Books. So be sure to document the API, and comment the code. Also, you do need to consider input filters to limit down the results, or at least a method to page results. Google once estimated there were 130 million books, so you want to get all of them at once?

这没关系,但项目的用户和未来的开发人员可能不清楚你有Book和Books这两个事实。所以一定要记录API,并注释代码。此外,您需要考虑输入过滤器来限制结果,或者至少考虑一种页面结果的方法。谷歌曾经估计有1.3亿本书,所以你想立刻获得所有书籍?

e.g. SERVER/Books/?skipRecords=0&limit=100

2.) The model 'Book' itself has a readAll function which loads all books.

2.)模型'Book'本身有一个readAll函数,可以加载所有书籍。

This is not ideal as it violates the single responsibility principal. Mostly it just doesn't make a lot of sense in an OO situation to have a single book entity be able to list all of it's sibling entities.

这并不理想,因为它违反了单一的责任主体。大多数情况下,在OO情况下,单个图书实体能够列出其所有兄弟实体并不是很有意义。

e.g. SERVER/TheHobbit/readAll.... yuck.

例如SERVER / TheHobbit / readAll ....哎呀。

3.) The model 'Book' is non-functional, it only has properties. Instead a 'BookStorage' class loads the data and fills one/multiple models. If you can expose these functional extension via the API, then that is an fine solution as well. It all comes down to documentation.

3.)模型'Book'不起作用,它只有属性。相反,'BookStorage'类加载数据并填充一个/多个模型。如果您可以通过API公开这些功能扩展,那么这也是一个很好的解决方案。这一切都归结为文档。

Maybe it ends up looking like this

也许最终看起来像这样

e.g.

SERVER/BookStorage/GetAllBooks?skipRecords=0&limit=100

SERVER/BookStorage/GetBook?title=TheHobbit

#2


3.) The model 'Book' is non-functional, it only has properties. Instead a 'BookStorage' class loads the data and fills one/multiple models.

3.)模型'Book'不起作用,它只有属性。相反,'BookStorage'类加载数据并填充一个/多个模型。

This approach is similar to the Repository Pattern and is very common. The BookStorage would be a BookRepository with an interface like:

这种方法类似于存储库模式,非常常见。 BookStorage将是一个BookRepository,其界面如下:

public interface IBookRepository
{
    Book GetById(int id);
    IEnumerable<Book> GetAll();
    // Other methods for creating, updating and deleting books.
}

In your controller you would then have:

在您的控制器中,您将拥有:

public class BooksController: ApiController
{
    private readonly IBookRepository _bookRepository;

    public BooksController(IBookRepository bookRepository)
    {
        _bookRepository = bookRepository;
    }

    [Route("api/books/{id}")]
    public IHttpActionResult Get(int id)
    {
        var book = _bookRepository.GetById(id);

        // ...
    }

    [Route("api/books")]
    public IHttpActionResult Get()
    {
        var books = _bookRepository.GetAll();

        // ...
    }
}

If you want paging, you would add a filter like Get(int page = 0) and then depending on your page size, use something like bookRepository.GetAll().Skip(PAGE_SIZE * page).Take(PAGE_SIZE).

如果你想要分页,你可以添加一个像Get(int page = 0)这样的过滤器,然后根据你的页面大小,使用像bookRepository.GetAll()这样的东西。跳过(PAGE_SIZE *页面).Take(PAGE_SIZE)。

A model should not load itself as it would violate the Single responsibility principle.

模型不应该加载自身,因为它违反了单一责任原则。

#3


A great resource when designing RESTful webservices is Martin Fowler's article Richardson Maturity Model.

设计RESTful Web服务时的一个很好的资源是Martin Fowler的文章Richardson Maturity Model。

To summarise for your book case:

总结您的书籍案例:

  • use POST /book to create a book
  • 使用POST / book来创建一本书

  • use PUT /book to update a book
  • 使用PUT / book来更新书籍

  • use GET /book?author=Shakespeare&year=1602&someOtherParam=someValue to find books
  • 使用GET / book?author = Shakespeare&year = 1602&someOtherParam = someValue查找书籍

  • use GET /book/:id to retrieve the details of a particular book
  • 使用GET / book /:id来检索特定书籍的详细信息

  • use DELETE /book/:id to delete a certain book
  • 使用DELETE / book /:id删除某本书

Also you might want to follow the HATEOAS principles, so you'll need to include all relevant links for a book into a links property, so that clients of your API will not need to build their own link when they'll want to add/edit/find/delete a book.

此外,您可能希望遵循HATEOAS原则,因此您需要将书籍的所有相关链接包含在链接属性中,以便API的客户在他们想要添加/时不需要构建自己的链接编辑/查找/删除一本书。

Although simple at first sight, designing a good RESTful webservice is not that easy, however HATEOAS helps because url schema changes on the server side don't affect clients as they don't hardcode the URL's needed for the CRUD operations. All you need to do is provide a starting point, e.g. a base url where all contents can be discovered, and clients can start from there navigating through your webservice.

虽然初看起来很简单,但设计一个好的RESTful Web服务并不容易,但HATEOAS会有所帮助,因为服务器端的url架构更改不会影响客户端,因为它们不会对CRUD操作所需的URL进行硬编码。您需要做的就是提供一个起点,例如:一个基本URL,可以发现所有内容,客户端可以从那里开始浏览您的Web服务。

#4


Read the CRUD Operations section here. You will find REST best practices to be followed when designing a REST API

请阅读此处的CRUD操作部分。您将在设计REST API时找到要遵循的REST最佳实践