Hands-On Docker for Microservices with Python
上QQ阅读APP看书,第一时间看更新

Handling resources

A typical RESTful application has the following general structure:

  1. A URL-defined resource. This resource allows one or more actions through HTTP methods (GET, POST, and so on).
  2. When each of the actions is called, the framework routes the request until the defined code executes the action.
  3. If there are any input parameters, they'll need to be validated first.
  4. Perform the action and obtain a result value. This action will normally involve one or more calls to the database, which will be done in the shape of models.
  5. Prepare the resulting result value and encode it in a way that's understood by the client, typically in JSON.
  6. Return the encoded value to the client with the adequate status code.

Most of these actions are done by the framework. Some configuration work needs to be done, but it's where our web framework, Flask-RESTPlus in this example, will help the most. In particular, everything but step 4 will be greatly simplified.

Let's take a look at a simple code example (available in GitHub) to describe it:

api_namespace = Namespace('api', description='API operations')

@api_namespace.route('/thoughts/<int:thought_id>/')
class ThoughtsRetrieve(Resource):

@api_namespace.doc('retrieve_thought')
@api_namespace.marshal_with(thought_model)
def get(self, thought_id):
'''
Retrieve a thought
'''
thought = ThoughtModel.query.get(thought_id)
if not thought:
# The thought is not present
return '', http.client.NOT_FOUND

return thought

This implements the GET /api/thoughts/X/ action, retrieving a single thought by ID.

Let's analyze each of the elements. Note the lines are grouped thematically:

  1. First, we define the resource by its URL. Note that api_namespace sets the api prefix to the URL, which validates that parameter X is an integer:
api_namespace = Namespace('api', description='API operations')

@api_namespace.route('/thoughts/<int:thought_id>/')
class ThoughtsRetrieve(Resource):
...
  1. The class allows you to perform multiple actions on the same resource. In this case, we only do one: the GET action.
  2. Note that the thought_id parameter, encoded in the URL, is passed as a parameter to the method:
class ThoughtsRetrieve(Resource):

def get(self, thought_id):
...
  1. We can now execute the action, which is a search in the database to retrieve a single object. Call ThoughModel to search for the specified thought. If found, it's returned with a http.client.OK (200) status code. If it's not found, an empty result and a http.client.NOT_FOUND 404 status code is returned:
def get(self, thought_id):
thought = ThoughtModel.query.get(thought_id)
if not thought:
# The thought is not present
return '', http.client.NOT_FOUND

return thought
  1. The thought object is being returned. The marshal_with decorator describes how the Python object should be serialized into a JSON structure. We'll see later how to configure it:
@api_namespace.marshal_with(thought_model)
def get(self, thought_id):
...
return thought
  1. Finally, we have some documentation, including the docstring that will be rendered by the autogenerated Swagger API:
class ThoughtsRetrieve(Resource):

@api_namespace.doc('retrieve_thought')
def get(self, thought_id):
'''
Retrieve a thought
'''
...

As you can see, most of the actions are configured and performed through Flask-RESTPlus, and the bulk of the work as a developer is the meaty step 4. There's work to do, though, configuring what the expected input parameters are and validating them, as well as how to serialize the returning object into proper JSON. We'll see how Flask-RESTPlus can help us with that.