REST-API 模型的最佳实践
Best practices for REST-API models
我正在研究 REST-API,运行 遇到架构问题。
模型 'Book' 表示具有属性和基于 CRUD 的函数的单本书。它通过读取函数从数据库加载自身。
但是,如果我想获取数据库中的所有书籍怎么办?当前的书籍模型不涵盖此用例。
我尝试了几种方法:
1.) 第二个模型叫做 'Books'。它有一个读取函数,其中 returns 一个图书对象列表。
2.) 模型'Book'本身有一个读取所有书籍的函数。
3.) 模型 'Book' 没有功能,它只有属性。相反,'BookStorage' class 加载数据并填充 one/multiple 模型。
我对这些方法都不满意。这种情况是否有最佳实践?
1.) A second model called 'Books'. It has a read function which returns a list of book objects.
这没关系,但项目的用户和未来的开发人员可能不清楚您同时拥有 Book 和 Books 的事实。所以一定要记录 API,并对代码进行注释。此外,您确实需要考虑输入过滤器来限制结果,或者至少需要一种对结果进行分页的方法。 Google曾经估计有1.3亿本书,你想一次搞定吗?
例如SERVER/Books/?skipRecords=0&limit=100
2.) The model 'Book' itself has a readAll function which loads all books.
这并不理想,因为它违反了单一责任原则。大多数情况下,在面向对象的情况下,让单个书籍实体能够列出它的所有兄弟实体并没有多大意义。
例如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.
也许它最终看起来像这样
例如
SERVER/BookStorage/GetAllBooks?skipRecords=0&limit=100
SERVER/BookStorage/GetBook?title=霍比特人
3.) The model 'Book' is non-functional, it only has properties. Instead a 'BookStorage' class loads the data and fills one/multiple models.
此方法与 Repository Pattern 类似,并且很常见。 BookStorage 将是一个 BookRepository,其接口如下:
public interface IBookRepository
{
Book GetById(int id);
IEnumerable<Book> GetAll();
// Other methods for creating, updating and deleting books.
}
在您的控制器中,您将拥有:
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();
// ...
}
}
如果你想要分页,你可以添加一个像 Get(int page = 0)
这样的过滤器,然后根据你的页面大小,使用像 bookRepository.GetAll().Skip(PAGE_SIZE * page).Take(PAGE_SIZE)
.
这样的东西
模型不应自行加载,否则会违反 Single responsibility principle。
Martin Fowler 的文章 Richardson Maturity Model 是设计 RESTful 网络服务的重要资源。
总结一下您的 book
案例:
- 使用
POST /book
创建一本书
- 使用
PUT /book
更新一本书
- 使用
GET /book?author=Shakespeare&year=1602&someOtherParam=someValue
查找书籍
- 使用
GET /book/:id
检索特定书籍的详细信息
- 使用
DELETE /book/:id
删除某本书
您可能还想遵循 HATEOAS 原则,因此您需要将一本书的所有相关 link 包含在 links
属性 中,这样 API 的客户在想要 add/edit/find/delete 一本书时就不需要构建自己的 link。
虽然乍一看很简单,但设计一个好的 RESTful 网络服务并不是那么容易,但是 HATEOAS 很有帮助,因为 url 服务器端的模式更改不会影响客户端,因为它们不会对 CRUD
操作所需的 URL 进行硬编码。您需要做的就是提供一个起点,例如一个基础 url 可以发现所有内容,客户可以从那里开始浏览您的网络服务。
阅读此处的 CRUD Operations 部分。在设计 REST API
时,您会发现要遵循的 REST 最佳实践
我正在研究 REST-API,运行 遇到架构问题。
模型 'Book' 表示具有属性和基于 CRUD 的函数的单本书。它通过读取函数从数据库加载自身。
但是,如果我想获取数据库中的所有书籍怎么办?当前的书籍模型不涵盖此用例。
我尝试了几种方法:
1.) 第二个模型叫做 'Books'。它有一个读取函数,其中 returns 一个图书对象列表。
2.) 模型'Book'本身有一个读取所有书籍的函数。
3.) 模型 'Book' 没有功能,它只有属性。相反,'BookStorage' class 加载数据并填充 one/multiple 模型。
我对这些方法都不满意。这种情况是否有最佳实践?
1.) A second model called 'Books'. It has a read function which returns a list of book objects.
这没关系,但项目的用户和未来的开发人员可能不清楚您同时拥有 Book 和 Books 的事实。所以一定要记录 API,并对代码进行注释。此外,您确实需要考虑输入过滤器来限制结果,或者至少需要一种对结果进行分页的方法。 Google曾经估计有1.3亿本书,你想一次搞定吗?
例如SERVER/Books/?skipRecords=0&limit=100
2.) The model 'Book' itself has a readAll function which loads all books.
这并不理想,因为它违反了单一责任原则。大多数情况下,在面向对象的情况下,让单个书籍实体能够列出它的所有兄弟实体并没有多大意义。
例如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.
也许它最终看起来像这样
例如
SERVER/BookStorage/GetAllBooks?skipRecords=0&limit=100
SERVER/BookStorage/GetBook?title=霍比特人
3.) The model 'Book' is non-functional, it only has properties. Instead a 'BookStorage' class loads the data and fills one/multiple models.
此方法与 Repository Pattern 类似,并且很常见。 BookStorage 将是一个 BookRepository,其接口如下:
public interface IBookRepository
{
Book GetById(int id);
IEnumerable<Book> GetAll();
// Other methods for creating, updating and deleting books.
}
在您的控制器中,您将拥有:
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();
// ...
}
}
如果你想要分页,你可以添加一个像 Get(int page = 0)
这样的过滤器,然后根据你的页面大小,使用像 bookRepository.GetAll().Skip(PAGE_SIZE * page).Take(PAGE_SIZE)
.
模型不应自行加载,否则会违反 Single responsibility principle。
Martin Fowler 的文章 Richardson Maturity Model 是设计 RESTful 网络服务的重要资源。
总结一下您的 book
案例:
- 使用
POST /book
创建一本书 - 使用
PUT /book
更新一本书 - 使用
GET /book?author=Shakespeare&year=1602&someOtherParam=someValue
查找书籍 - 使用
GET /book/:id
检索特定书籍的详细信息 - 使用
DELETE /book/:id
删除某本书
您可能还想遵循 HATEOAS 原则,因此您需要将一本书的所有相关 link 包含在 links
属性 中,这样 API 的客户在想要 add/edit/find/delete 一本书时就不需要构建自己的 link。
虽然乍一看很简单,但设计一个好的 RESTful 网络服务并不是那么容易,但是 HATEOAS 很有帮助,因为 url 服务器端的模式更改不会影响客户端,因为它们不会对 CRUD
操作所需的 URL 进行硬编码。您需要做的就是提供一个起点,例如一个基础 url 可以发现所有内容,客户可以从那里开始浏览您的网络服务。
阅读此处的 CRUD Operations 部分。在设计 REST API
时,您会发现要遵循的 REST 最佳实践