mirror of
				https://github.com/yiisoft/yii2.git
				synced 2025-11-04 06:37:55 +08:00 
			
		
		
		
	
		
			
				
	
	
		
			108 lines
		
	
	
		
			4.4 KiB
		
	
	
	
		
			Markdown
		
	
	
	
	
	
			
		
		
	
	
			108 lines
		
	
	
		
			4.4 KiB
		
	
	
	
		
			Markdown
		
	
	
	
	
	
Versioning
 | 
						|
==========
 | 
						|
 | 
						|
Your APIs should be versioned. Unlike Web applications which you have full control on both client side and server side
 | 
						|
code, for APIs you usually do not have control of the client code that consumes the APIs. Therefore, backward
 | 
						|
compatibility (BC) of the APIs should be maintained whenever possible, and if some BC-breaking changes must be
 | 
						|
introduced to the APIs, you should bump up the version number. You may refer to [Semantic Versioning](http://semver.org/)
 | 
						|
for more information about designing the version numbers of your APIs.
 | 
						|
 | 
						|
Regarding how to implement API versioning, a common practice is to embed the version number in the API URLs.
 | 
						|
For example, `http://example.com/v1/users` stands for `/users` API of version 1. Another method of API versioning
 | 
						|
which gains momentum recently is to put version numbers in the HTTP request headers, typically through the `Accept` header,
 | 
						|
like the following:
 | 
						|
 | 
						|
```
 | 
						|
// via a parameter
 | 
						|
Accept: application/json; version=v1
 | 
						|
// via a vendor content type
 | 
						|
Accept: application/vnd.company.myapp-v1+json
 | 
						|
```
 | 
						|
 | 
						|
Both methods have pros and cons, and there are a lot of debates about them. Below we describe a practical strategy
 | 
						|
of API versioning that is kind of a mix of these two methods:
 | 
						|
 | 
						|
* Put each major version of API implementation in a separate module whose ID is the major version number (e.g. `v1`, `v2`).
 | 
						|
  Naturally, the API URLs will contain major version numbers.
 | 
						|
* Within each major version (and thus within the corresponding module), use the `Accept` HTTP request header
 | 
						|
  to determine the minor version number and write conditional code to respond to the minor versions accordingly.
 | 
						|
 | 
						|
For each module serving a major version, it should include the resource classes and the controller classes
 | 
						|
serving for that specific version. To better separate code responsibility, you may keep a common set of
 | 
						|
base resource and controller classes, and subclass them in each individual version module. Within the subclasses,
 | 
						|
implement the concrete code such as `Model::fields()`.
 | 
						|
 | 
						|
Your code may be organized like the following:
 | 
						|
 | 
						|
```
 | 
						|
api/
 | 
						|
    common/
 | 
						|
        controllers/
 | 
						|
            UserController.php
 | 
						|
            PostController.php
 | 
						|
        models/
 | 
						|
            User.php
 | 
						|
            Post.php
 | 
						|
    modules/
 | 
						|
        v1/
 | 
						|
            controllers/
 | 
						|
                UserController.php
 | 
						|
                PostController.php
 | 
						|
            models/
 | 
						|
                User.php
 | 
						|
                Post.php
 | 
						|
        v2/
 | 
						|
            controllers/
 | 
						|
                UserController.php
 | 
						|
                PostController.php
 | 
						|
            models/
 | 
						|
                User.php
 | 
						|
                Post.php
 | 
						|
```
 | 
						|
 | 
						|
Your application configuration would look like:
 | 
						|
 | 
						|
```php
 | 
						|
return [
 | 
						|
    'modules' => [
 | 
						|
        'v1' => [
 | 
						|
            'basePath' => '@app/modules/v1',
 | 
						|
        ],
 | 
						|
        'v2' => [
 | 
						|
            'basePath' => '@app/modules/v2',
 | 
						|
        ],
 | 
						|
    ],
 | 
						|
    'components' => [
 | 
						|
        'urlManager' => [
 | 
						|
            'enablePrettyUrl' => true,
 | 
						|
            'enableStrictParsing' => true,
 | 
						|
            'showScriptName' => false,
 | 
						|
            'rules' => [
 | 
						|
                ['class' => 'yii\rest\UrlRule', 'controller' => ['v1/user', 'v1/post']],
 | 
						|
                ['class' => 'yii\rest\UrlRule', 'controller' => ['v2/user', 'v2/post']],
 | 
						|
            ],
 | 
						|
        ],
 | 
						|
    ],
 | 
						|
];
 | 
						|
```
 | 
						|
 | 
						|
As a result, `http://example.com/v1/users` will return the list of users in version 1, while
 | 
						|
`http://example.com/v2/users` will return version 2 users.
 | 
						|
 | 
						|
Using modules, code for different major versions can be well isolated. And it is still possible
 | 
						|
to reuse code across modules via common base classes and other shared classes.
 | 
						|
 | 
						|
To deal with minor version numbers, you may take advantage of the content negotiation
 | 
						|
feature provided by the [[yii\filters\ContentNegotiator|contentNegotiator]] behavior. The `contentNegotiator`
 | 
						|
behavior will set the [[yii\web\Response::acceptParams]] property when it determines which
 | 
						|
content type to support.
 | 
						|
 | 
						|
For example, if a request is sent with the HTTP header `Accept: application/json; version=v1`,
 | 
						|
after content negotiation, [[yii\web\Response::acceptParams]] will contain the value `['version' => 'v1']`.
 | 
						|
 | 
						|
Based on the version information in `acceptParams`, you may write conditional code in places
 | 
						|
such as actions, resource classes, serializers, etc.
 | 
						|
 | 
						|
Since minor versions require maintaining backward compatibility, hopefully there are not much
 | 
						|
version checks in your code. Otherwise, chances are that you may need to create a new major version.
 |