如何设计更好的WebAPI

随着移动互联网和Web开发技术的发展,在项目中需要为越来越多的跨平台应用提供统一化的API接口。那么作为一个后端开发者,如何设计并开发出更规范、更清晰、更好用的WebAPI呢?


1.如何理解API?

宽泛的讲,API(Application Programming Interface)指的是应用程序编程接口,用来提供预先定义好的方法和函数,使用者无需去关心其内部细节就能实现相应的功能。而现在我们提到API,脑海中首先想到的往往是基于HTTP协议传输的WebService技术。
从概念上讲,API与开发语言无关,理论上具备网络编程功能的编程语言,如Java、C#、PHP、Node等,都能够通过相应HTTP请求并构造HTTP包来实现Web API,虽然它们内部的实现原理不同,但确确实实的提供了API服务。
对于.NET平台的开发者来讲,使用C#编程语言有WCF、WebService、ASP.NET WebAPI等多种技术方案。由于ASP.NET WebAPI简单轻量的特点,使其受到越来越多人的欢迎。

2.什么是好的WebAPI?

从我个人的角度理解,暂且不论是什么编程语言,WebAPI最终是要提供给第三方使用的,可能是你的同事或者合作伙伴,所以后端开发人员提供的WebAPI可以看作是个人或者团队的产品输出。从产品的特质来看,WebAPI首先要能满足使用要求,其次要具备标准化、稳定性等特征。如果能在这些特征基础上为使用者提供更多额外的功能,那才是好的WebAPI。
举个通俗的例子,我们在生活中都会用到插线板,不管哪个品牌的插线板都遵循同样的标准。如果在中国大陆提供不了220V标准电压,废品;如果电器插座插不进插孔,废品;如果使用一小时寿命就终结,废品…只有在提供安全、稳定、标准供电的基础上再去考虑彩色设计、卡通造型、防水保护、耗能统计这些附加功能,才能说是一个好的插线板。
同样现在很多WebAPI只是解决了能用的问题,但是否好用还要细细思量。来看一个我们之前的WebAPI示例。

Method:GET
URL:api/TestResults/GetView_ResultList?pagesize={pagesize}&currentpage={currentpage}&where={where}&orderby={orderby}
Request:
Response:{ “sample string 1”: “sample string 2”,“sample string 3”: “sample string 4”}

很多前端开发人员看到这个API接口,心里肯定会想WTF?但遗憾的是,当我们刚开始接触WebAPI时,大量的接口都是这种形式的,现在回头来看真的是很糟糕。没有文档、URL复杂、没有返回状态,除了开发者本人没人知道这个接口要干嘛。幸运的是随着对WebAPI的深入了解,我们开始认识到设计一套清晰易用的WebAPI是多么重用,并开始将WebAPI的设计向一些成熟的规范和约定靠拢,其中之一就RESTful风格的API设计。

3.RESTful风格的API设计

REST是“REpresentational State Transfer”的缩写,可以翻译成“表现状态转换”,理解起来比较抽象。REST是一种架构风格,并不是一个技术框架也非强制规范,而是通过对资源的定位、操作藉由HTTP协议提供服务,其中资源可以是数据、图片、流程等具体的或抽象的对象。简单来说就是通过定义URI和HTTP状态码,让API更加简洁清晰、易懂易记、富有层次。通过RESTful风格的API可以解决我们在WebAPI开发过程中遇到的一些问题。

3.1.如何让 URI 清晰易懂?

好的代码无需注释,代码即注释。对于API也一样,好的URI设计无需文档就能理解用途。在API接口的URL中应仅使用名词而不是动词。此外所有名词小写,同时注意复数与单数的使用差异。

/orders             所有订单
/orders/12          具有指定标识的订单
/orders?sort=asc    按照升序排列订单
3.2.如何最大程度地利用 HTTP 协议?

合理利用HTTP协议本身的方法GET、POST、PUT、DELETE。如果说上面的URI对资源进行定位,那么HTTP方法就是对资源进行操作,几个典型的用法有:

HTTP方法用途示例
GET获取数据集合/orders
GET获取指定数据/orders/{id}
POST新建数据/orders
PUT完整更新数据/orders/{id}
PATCH部分更新数据/orders/{id}
DELETE删除数据/orders/{id}
3.3.如何选择数据格式?

使用JSON或XML传输数据,其中JSON阅读性更高,且更易扩展,能够使用多种环境和编程语言。

{
    status:200,
    data:[
    		{"id":1,"name":"订单1"},
    		{"id":2,"name":"订单2"},
    		{"id":3,"name":"订单3"}
    	],
    msg:""
}
3.4.如何通过状态码表示出错信息?

充分使用HTTP状态码。我们知道HTTP协议是Request-Response模式的,针对各种情况的HTTP请求,服务器应该反馈恰当的状态码。如果我们把错误信息放在状态码为200的反馈中,势必会造成混乱。所以完全可以利用状态码将HTTP自身的错误和服务器内部的错误区分开。

状态码含义用途
200OK表示请求成功,例如GET请求或更新、删除资源成功。
201Created表示POST创建资源成功。
204No Content表示请求已处理成功,但未返回任何内容。
400Bad Request表示服务器无法理解客户端的请求内容。
401Unauthorized表示不允许客户端访问资源,需要客户端自查传入的参数是否满足条件。
403Forbidden表示请求有效且客户端通过身份认证,但不允许客户端访问资源。
404Not Found表示请求的资源不存在。
415Unsupported Media Type表示服务器媒体类型Content-Type或Accept出现错误。
500Internal Server Error表示服务器内部错误
503Service Unavailable表示服务器已关闭或无法接受、处理请求。
3.5.如何规划 API 的版本?

在URI中加入版本信息,可以通过仅仅修改版本号就实现API服务的迁移,而无需修改所有API接口的地址。

/v1/orders
/v2/orders
3.6.如何通过查询参数实现搜索分页?
/v1/orders?sort=date     按照条件排序
/v1/orders?page=4        按照页码分页
/v1/orders?filter=xxx    按照条件过滤  
3.7.如何定义返回数据结构?

用结构化的JSON数据填充HTTP Response的Body。参考JSON API规范如何传递数据的格式。在顶级结点使用data、errors、meta来描述数据、错误信息和元信息,其中data和errors不能在同一个返回数据中出现,meta一般情况下可以不用。

JSON API文档

示例
{
  "meta": {
    "version":"2.1",
    "copyright": "Copyright 2015 Example Corp.",
    "authors": [
      "Yehuda Katz",
      "Steve Klabnik"
    ]
  },
  "data": {
    // 数据主体
  }
}
data
{
  "data": {
    "type": "orders",
    "id": "1",
    "attributes": {
      // 资源的具体数据
    },
    "relationships": {
      // 资源的可选属性,如关联数据、资源地址等
    }
  }
}

其中type描述数据的类型,可以对应为数据模型的类名;id为资源的唯一标识。

errors
{
	"errors":[
		{
			"code":10001,
			"title":"orderNo is null"
		},
		{
			"code":10002,
			"title":"the result can't be null",
			"detail":""
		}
	]
}

其中code为具体的错误码,title为错误信息,在开发测试环境下可以添加detail字段用来放置更详细的错误信息。

4.如何在ASP.NET WebAPI实现RESTful风格?

4.1.基本配置
    public static class WebApiConfig
    {
        public static void Register(HttpConfiguration config)
        {
            // Web API 配置和服务

            // Web API 路由
            config.MapHttpAttributeRoutes();

            config.Routes.MapHttpRoute(
                name: "DefaultApi",
                routeTemplate: "api/v1/{controller}/{id}",
                defaults: new { id = RouteParameter.Optional }
            );
        }
    }
4.2.API控制器代码
    public class ClientController : ApiController
    {
        private DBEntities db = new DBEntities();
        /// <summary>
        /// 获取全部资源
        /// </summary>
        /// <returns></returns>
        public IQueryable<SYS_Client> GetClients()
        {
            return db.SYS_Client;
        }

        /// <summary>
        /// 获取指定标识的资源
        /// </summary>
        /// <param name="id"></param>
        /// <returns></returns>
        [ResponseType(typeof(SYS_Client))]
        public async Task<IHttpActionResult> GetClientById(string id)
        {
            SYS_Client client = await db.SYS_Client.FindAsync(id);
            if (client == null)
            {
                return NotFound();
            }

            return Ok(client);
        }

        /// <summary>
        /// 更新资源
        /// </summary>
        /// <param name="id"></param>
        /// <param name="client"></param>
        /// <returns></returns>
        [ResponseType(typeof(void))]
        public async Task<IHttpActionResult> PutSYS_Client(string id, SYS_Client client)
        {
            if (!ModelState.IsValid)
            {
                return BadRequest(ModelState);
            }

            if (id != client.RowGuid)
            {
                return BadRequest();
            }

            db.Entry(client).State = EntityState.Modified;

            try
            {
                await db.SaveChangesAsync();
            }
            catch (DbUpdateConcurrencyException)
            {
                if (!IsExists(id))
                {
                    return NotFound();
                }
                else
                {
                    throw;
                }
            }

            return StatusCode(HttpStatusCode.NoContent);
        }

        /// <summary>
        /// 新增资源
        /// </summary>
        /// <param name="client"></param>
        /// <returns></returns>
        [ResponseType(typeof(SYS_Client))]
        public async Task<IHttpActionResult> PostSYS_Client(SYS_Client client)
        {
            if (!ModelState.IsValid)
            {
                return BadRequest(ModelState);
            }

            db.SYS_Client.Add(client);

            try
            {
                await db.SaveChangesAsync();
            }
            catch (DbUpdateException)
            {
                if (IsExists(client.RowGuid))
                {
                    return Conflict();
                }
                else
                {
                    throw;
                }
            }

            return CreatedAtRoute("DefaultApi", new { id = client.RowGuid }, client);
        }

        /// <summary>
        /// 删除资源
        /// </summary>
        /// <param name="id"></param>
        /// <returns></returns>
        [ResponseType(typeof(SYS_Client))]
        public async Task<IHttpActionResult> DeleteSYS_Client(string id)
        {
            SYS_Client client = await db.SYS_Client.FindAsync(id);
            if (client == null)
            {
                return NotFound();
            }

            db.SYS_Client.Remove(client);
            await db.SaveChangesAsync();

            return Ok(client);
        }

        protected override void Dispose(bool disposing)
        {
            if (disposing)
            {
                db.Dispose();
            }
            base.Dispose(disposing);
        }

        private bool IsExists(string id)
        {
            return db.SYS_Client.Count(e => e.RowGuid == id) > 0;
        }
    }

4.3.API清单

在这里插入图片描述


以上内容只是抛砖引玉,如果想要设计和开发更好用的WebAPI,无疑要经过更多的改进和优化。只有不断进步,才能输出更好的产品。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值