@xiaoyixy
2020-07-16T10:21:04.000000Z
字数 10180
阅读 806
API设计
Learning REST in pieces is one thing, while applying all those learned concepts into real application design is completely another challenge. In this tutorial, we will learn to design REST APIs for a network based application. Please note that the takeaway from this whole exercise is the learning of how to apply REST principles in design process.
Steps in designing REST Services
The very first step in designing a REST API based application is – identifying the objects which will be presented as resources.
For a network based application, object modeling is pretty much simpler. There can be many things such as devices, managed entities, routers, modems etc. For simplicity sake, we will consider only two resources i.e.
Here configuration is sub-resource of a device. A device can have many configuration options.
Note that both objects/resources in our above model will have a unique identifier, which is the integer id property.
Now when object model is ready, it’s time to decide the resource URIs. At this step, while designing the resource URIs[HTML Preview] – focus on the relationship between resources and its sub-resources. These resource URIs are endpoints for RESTful services.
In our application, a device is a top-level resource. And configuration is sub-resource under device. Let’s write down the URIs.
/devices
/devices/{id}
/configurations
/configurations/{id}
/devices/{id}/configurations
/devices/{id}/configurations/{id}
Notice that these URIs do not use any verb or operation. It’s very important to not include any verb in URIs. URIs should all be nouns only.
Now when resource URIs have been decided, let’s work on their representations. Mostly representations are defined in either XML or JSON format. We will see XML examples as its more expressive on how data is composed.
When returning a collection resource, include only most important information about resource. This will keep the size of payload small, and so will improve the performance of REST APIs.
<devices size="2">
<link rel="self" href="/devices"/>
<device id="12345">
<link rel="self" href="/devices/12345"/>
<deviceFamily>apple-es</deviceFamily>
<OSVersion>10.3R2.11</OSVersion>
<platform>SRX100B</platform>
<serialNumber>32423457</serialNumber>
<connectionStatus>up</connectionStatus>
<ipAddr>192.168.21.9</ipAddr>
<name>apple-srx_200</name>
<status>active</status>
</device>
<device id="556677">
<link rel="self" href="/devices/556677"/>
<deviceFamily>apple-es</deviceFamily>
<OSVersion>10.3R2.11</OSVersion>
<platform>SRX100B</platform>
<serialNumber>6453534</serialNumber>
<connectionStatus>up</connectionStatus>
<ipAddr>192.168.20.23</ipAddr>
<name>apple-srx_200</name>
<status>active</status>
</device>
</devices>
Opposite to collection URI, here include complete information of a device in this URI. Here, also include a list of links for sub-resources and other supported operations. This will make your REST API HATEOAS driven.
<device id="12345">
<link rel="self" href="/devices/12345"/>
<id>12345</id>
<deviceFamily>apple-es</deviceFamily>
<OSVersion>10.0R2.10</OSVersion>
<platform>SRX100-LM</platform>
<serialNumber>32423457</serialNumber>
<name>apple-srx_100_lehar</name>
<hostName>apple-srx_100_lehar</hostName>
<ipAddr>192.168.21.9</ipAddr>
<status>active</status>
<configurations size="2">
<link rel="self" href="/configurations" />
<configuration id="42342">
<link rel="self" href="/configurations/42342" />
</configuration>
<configuration id="675675">
<link rel="self" href="/configurations/675675" />
</configuration>
</configurations>
<method href="/devices/12345/exec-rpc" rel="rpc"/>
<method href="/devices/12345/synch-config"rel="synch device configuration"/>
</device>
Similar to device collection representation, create configuration collection representation with only minimal information.
<configurations size="20">
<link rel="self" href="/configurations" />
<configuration id="42342">
<link rel="self" href="/configurations/42342" />
</configuration>
<configuration id="675675">
<link rel="self" href="/configurations/675675" />
</configuration>
...
...
</configurations>
Please note that configurations collection representation inside device is similar to top-level configurations URI. Only difference is that configurations for a device are only two, so only two configuration items are listed as subresource under device.
Now, single configuration resource representation must have all possible information about this resource – including relevant links.
<configuration id="42342">
<link rel="self" href="/configurations/42342" />
<content><![CDATA[...]]></content>
<status>active</status>
<link rel="raw configuration content" href="/configurations/42342/raw" />
</configuration>
This resource collection of configurations will be a subset of primary collection of configurations, and will be specific a device only. As it is the subset of primary collection, DO NOT create a different representation data fields than primary collection. Use same presentation fields as primary collection.
<configurations size="2">
<link rel="self" href="/devices/12345/configurations" />
<configuration id="53324">
<link rel="self" href="/devices/12345/configurations/53324" />
<link rel="detail" href="/configurations/53324" />
</configuration>
<configuration id="333443">
<link rel="self" href="/devices/12345/configurations/333443" />
<link rel="detail" href="/configurations/333443" />
</configuration>
</configurations>
Notice that this subresource collection has two links. One for its direct representation inside sub-collection i.e. /devices/12345/configurations/333443 and other pointing to its location in primary collection i.e. /configurations/333443.
Having two links is important as you can provide access to a device specific configuration in more unique manner, and you will have ability to mask some fields (if design require it) which shall not be visible in a secondary collection.
This representation should have either exactly similar representation as of Configuration representation from primary collection; OR you may mask few fields.
This subresource representation will also have an additional link to its primary presentation.
<configuration id="11223344">
<link rel="self" href="/devices/12345/configurations/11223344" />
<link rel="detail" href="/configurations/11223344" />
<content><![CDATA[...]]></content>
<status>active</status>
<link rel="raw configuration content" href="/configurations/11223344/raw" />
</configuration>
Now, before moving forward to next section, let’s note down few observations so you don’t miss them.
So our resource URIs and their representation are fixed now. Let’s decide the possible operations in application and map these operations on resource URIs. A user of network application can perform browse, create, update or delete operations. So let’s map them.
HTTP GET /devices
HTTP GET /configurations
If the collection size is large, you can apply paging and filtering as well. e.g. Below requests will fetch first 20 records from collection.
HTTP GET /devices?startIndex=0&size=20
HTTP GET /configurations?startIndex=0&size=20
HTTP GET /devices/{id}/configurations
It will be mostly a small size collection – so no need to enable filtering or soring here.
Browse single device or configuration [Primary Collection]
To get the complete detail of a device or configuration, use GET operation on singular resource URIs.
HTTP GET /devices/{id}
HTTP GET /configurations/{id}
HTTP GET /devices/{id}/configurations/{id}
Subresource representation will be either same as or subset of primary presentation.
Create is not idempotent operation, and in HTTP protocol – POST is also not idempotent. So use POST.
HTTP POST /devices
HTTP POST /configurations
Please note that request payload will not contain any id attribute, as server is responsible for deciding it. Response of create request will look like this:
HTTP/1.1 201 Created
Content-Type: application/xml
Location: http://example.com/network-app/configurations/678678
<configuration id="678678">
<link rel="self" href="/configurations/678678" />
<content><![CDATA[...]]></content>
<status>active</status>
<link rel="raw configuration content" href="/configurations/678678/raw" />
</configuration>
Update operation is an idempotent operation and HTTP PUT is also is idempotent method. So we can use PUT method for update operations.
HTTP PUT /devices/{id}
HTTP PUT /configurations/{id}
PUT response may look like this.
HTTP/1.1 200 OK
Content-Type: application/xml
<configuration id="678678">
<link rel="self" href="/configurations/678678" />
<content><![CDATA[. updated content here .]]></content>
<status>active</status>
<link rel="raw configuration content" href="/configurations/678678/raw" />
</configuration>
Removing is always a DELETE operation.
HTTP DELETE /devices/{id}
HTTP DELETE /configurations/{id}
A successful response SHOULD be 202 (Accepted) if resource has been queues for deletion (async operation), or 200 (OK) / 204 (No Content) if resource has been deleted permanently (sync operation).
In case of async operation, application shall return a task id which can be tracked for success/failure status.
Please note that you should put enough analysis in deciding the behavior when a subresource is deleted from system. Normally, you may want to SOFT DELETE a resource in these requests – in other words, set their status INACTIVE. By following this approach, you will not need to find and remove its references from other places as well.
In real application, you will need to apply the configuration on device – OR you may want to remove the configuration from device (not from primary collection). You shall use PUT and DELETE methods in this case, because of its idempotent nature.
//Apply Configuration on a device
HTTP PUT /devices/{id}/configurations
//Remove Configuration on a device
HTTP DELETE /devices/{id}/configurations/{id}
So far we have designed only object model, URIs and then decided HTTP methods or operations on them. You need to work on other aspects of the application as well:
1) Logging
2) Security
3) Discovery etc.