作为一名 Ruby 程序员,在工作中,除了开发 Web 网站类项目。 最近一年多的时间,有机会和前端工程师(iOS和Android)一起配合开发了几款 APP 产品。
做服务端 API 设计和实现与 Web 开发有很大的不同。 从最开始的不知所措,到现在已经摸索出一条,比较清晰的设计和实现思路。 之前总结过一篇博客,讲的的是 Web API 设计最佳实践: Web API Design - Best Practices 里面的很多观点,现在看来还是很实用。但是当时没有讲解具体的设计和代码实现。
首先,做 API 的设计和开发,主要是为前端提供展示需要的数据,以及辅助前端完成业务的逻辑实现。 看似很简单的一个事情,但是实际的项目开发过程中:每个版本的迭代,在业务需求,交互,UI 界面上都有较大的变更。 因此,每个版本的 API 在业务实现,以及返回参数上,势必存在很大的差异。 然而,每个新版本的发布,后端都需要兼容,之前所有发布的版本。
解决思路是采用版本隔离和分层设计。 版本隔离:即在业务有重大调整时,将接口版本号由 v1 升级到 v2,在保证 v1 所有接口和功能稳定的情况下, v2 接口可以自由的开发实现。 分层设计:分层的目的是为了将复杂的、集中的程序实现,拆分成多个、细小的模块, 每个模块各自实现独立的功能,组合起来完成一个事情。 分层可以将复杂问题简单化,单一职责的程序更容易理解、实现、维护和测试。
最近一个项目中的 API 设计如下图(画的比较简陋)。
1. API 层 实现接口版本控制,接口请求参数处理和验证,接口返回数据处理。实例代码如下:
module V1 class SchoolApi < BaseApi resource :schools do desc "获取学校列表接口" params do optional :page, :type => Integer, :desc => "Page number" end get do authentication content = V1::SchoolService.list(params[:page]) render_json content end end end end
2. Service 层 实现接口的业务逻辑。实例代码如下:
module V1 class SchoolService class << self def list(page) schools = School.paginate(page) V1::SchoolWrapper.schools_info(schools) end end end end
3. Wrapper 层 封装接口返回的具体数据。实例代码如下:
module V1 class SchoolWrapper < BaseWrapper class << self def schools_info(schools) ret = { content: {} } if has_size?(schools) ret[:content][:schools] = [] schools.each do |school| ret[:content][:schools] << detail(school) end end ret end def detail(school) { id: school.id, name: school.real_name } end end end end
4. 其他 其他主要包括 Model, Utils, Gems,以及发邮件短信等项目基础组件。
总结以上的 API 设计和实现。 优势:每个版本的 API 接口,有很好的隔离;每个版本的业务逻辑,独立实现,不易产生冲突,易于维护; 因为独立实现,所以对需求变更的适应性强 劣势:程序文件和代码量会增加。
BTW 项目里引入 Grape 有几点考虑:RESTful APIs;版本管理;接口参数管理;路由管理 用 Rails 开发久了,比较习惯用 Controller 里的 before_filter 之类的过滤器。 但是 Grape 是没有过滤器的,所以开发的时候,这一点比较别扭。
2013-11-03