使用Spring Boot和Swagger进行API优先开发 - reflectoring.io


遵循API优先方法,我们在开始编码之前先指定一个API。通过API描述语言,团队可以进行协作而无需执行任何操作。
使用OpenAPI,我们可以创建一个API规范,我们可以在团队之间共享以交流合同。OpenAPI Maven插件使我们可以根据这样的规范为Spring Boot生成样板代码,因此我们只需要自己实现业务逻辑即可。
这些描述语言指定了端点,安全性模式,对象模式等。而且,大多数时候我们也可以生成这样的规范代码。通常,API规范也成为该API的文档。
您可以在GitHub上浏览示例代码。

API优先的好处
要开始进行组件或系统之间的集成,团队需要签订合同。在我们的案例中,合同是API规范。API-first帮助团队之间相互通信,而无需实现任何事情。它还使团队可以并行工作。
API优先方法的亮点在于构建更好的API。仅关注需要提供的功能。简约的API意味着需要维护的代码更少。

使用Swagger编辑器创建API规范
让我们在YAML文档中创建自己的OpenAPI规范。为了更容易理解,我们将讨论分为正在创建的YAML文档的各个部分。如果您想了解有关OpenAPI规范的更多详细信息,可以访问Github存储库
我们从文档顶部的一些有关API的常规信息开始:

openapi: 3.0.2
info:
  title: Reflectoring
  description: "Tutorials on Spring Boot and Java."
  termsOfService: http:
//swagger.io/terms/
  contact:
    email: petros.stergioulas94@gmail.com
  license:
    name: Apache 2.0
    url: http:
//www.apache.org/licenses/LICENSE-2.0.html
  version: 0.0.1-SNAPSHOT
externalDocs:
  description: Find out more about Reflectoring
  url: https:
//reflectoring.io/about/
servers:
- url: https:
//reflectoring.swagger.io/v2

openapi字段允许我们定义文档遵循的OpenAPI规范的版本。
在这一info部分中,我们添加了有关API的一些信息。这些字段应该是不言自明的。
最后,在本servers节中,我们提供了实现API的服务器列表。

然后是关于我们的API的一些其他元数据:

tags:
- name: user
  description: Operations about user
  externalDocs:
    description: Find out more about our store
    url: http://swagger.io

tags部分提供了其他元数据的字段,我们可以使用这些字段使我们的API更具可读性并易于遵循。我们可以添加多个标签,但是每个标签都应该是唯一的。

接下来,我们将描述一些路径。路径保存有关单个端点及其操作的信息:

paths:
  /user/{username}:
    get:
      tags:
      - user
      summary: Get user by user name
      operationId: getUserByName
      parameters:
      - name: username
        in: path
        description: 'The name that needs to be fetched. '
        required: true
        schema:
          type: string
      responses:
        200:
          description: successful operation
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/User'
        404:
          description: User not found
          content: {}

$ref字段允许我们引用自定义模式中的对象。在这种情况下,我们指的是User架构对象(请参阅下一节有关Components)。summary是该操作的简短说明。使用operationId,我们可以为操作定义唯一的标识符。我们可以将其视为我们的方法名称。最后,responses对象允许我们定义操作的结果。我们必须为任何操作调用至少定义一个成功的响应代码。

组件
本components节中全部介绍了API的对象。除非我们从组件对象外部的属性中明确引用了它们,否则在组件对象中定义的对象将不会影响API,如上所述:

components:
  schemas:
    User:
      type: object
      properties:
        id:
          type: integer
          format: int64
        username:
          type: string
        firstName:
          type: string
        ... more attributes
        userStatus:
          type: integer
          description: User Status
          format: int32
  securitySchemes:
    reflectoring_auth:
      type: oauth2
      flows:
        implicit:
          authorizationUrl: http://reflectoring.swagger.io/oauth/dialog
          scopes:
            write:users: modify users
            read:users: read users
    api_key:
      type: apiKey
      name: api_key
      in: header

schemas部分允许我们定义要在API中使用的对象。在本securitySchemes节中,我们可以定义操作可以使用的安全性方案。有两种使用安全方案的可能方法。
首先,我们可以使用security字段将安全方案添加到特定操作:

paths:
  /user/{username}:
    get:
      tags:
      - user
      summary: Get user by user name
      security: 
        - api_key: []

在上面的示例中,我们明确指定使用api_key我们上面定义的方案保护路径/ user / {username} 。
但是,如果我们要在整个项目中应用安全性,则只需将其指定为顶级字段即可:

paths:
  /user/{username}:
    get:
      tags:
      - user
      summary: Get user by user name
security: 
  - api_key: []

现在,我们的所有路径都应通过该api_key方案来保证。

从API规范生成代码
定义了API之后,我们现在将根据上面YAML文档创建代码。
我们将研究两种不同的生成代码的方法:


1.从Swagger编辑器生成代码
尽管这是我不会采用的方法,但让我们讨论一下并讨论为什么我认为这是一个坏主意。
让我们转到Swagger Editor,然后将我们的YAML文件粘贴到其中。然后,我们从菜单中选择“ 生成服务器”,然后选择我们要生成哪种服务器(我使用“ Spring”)。
那么,为什么这是个坏主意呢?
首先,为我生成的代码是使用Java 7和Spring Boot 1.5.22,它们都已经过时了。
其次,如果我们对规范进行更改(并且更改始终在发生),我们将不得不复制并粘贴手动更改的文件。

2.使用OpenAPI Maven插件生成代码
更好的替代方法是使用OpenAPI Maven插件从Maven构建中生成代码。
让我们看一下文件夹结构。我选择使用一个多模块Maven项目,其中有两个项目:

  • app,它是根据我们的规范实现API的应用程序。
  • specification,其唯一的工作就是为我们的应用提供API规范。

文件夹结构如下所示:
spring-boot-openapi
├── app
│   └── pom.xml
│   └── src
│       └── main
│           └── java
│               └── io.reflectoring
│                   └── OpenAPIConsumerApp.java
├── specification
│   └── pom.xml
│   └── src
│       └── resources
│           └── openapi.yml
└── pom.xml

为了简单起见,我们省略了测试文件夹。
我们app是一个简单的Spring启动的项目,我们可以自动生成上start.spring.io,让我们着眼于pom.xml从specification模块,在那里我们配置的OpenAPI Maven插件:

<plugin>
    <groupId>org.openapitools</groupId>
    <artifactId>openapi-generator-maven-plugin</artifactId>
    <version>4.2.3</version>
    <executions>
        <execution>
            <goals>
                <goal>generate</goal>
            </goals>
            <configuration>
                <inputSpec>
                  ${project.basedir}/src/main/resources/openapi.yml
                </inputSpec>
                <generatorName>spring</generatorName>
                <apiPackage>io.reflectoring.api</apiPackage>
                <modelPackage>io.reflectoring.model</modelPackage>
                <supportingFilesToGenerate>
                  ApiUtil.java
                </supportingFilesToGenerate>
                <configOptions>
                    <delegatePattern>true</delegatePattern>
                </configOptions>
            </configuration>
        </execution>
    </executions>
</plugin>

您可以在GitHub上查看完整pom.xml文件。
在本教程中,我们使用spring生成器。
简单地运行命令./mvnw install将生成实现我们的OpenAPI规范的代码!
查看文件夹target/generated-sources/openapi/src/main/java/io/reflectoring/model,我们找到了User在YAML中定义的模型的代码:

@javax.annotation.Generated(...)
public class User   {
  @JsonProperty("id")
  private Long id;

  @JsonProperty(
"username")
  private String username;

  @JsonProperty(
"firstName")
  private String firstName;
  
 
// ... more properties

  @JsonProperty(
"userStatus")
  private Integer userStatus;

 
// ... getters and setters

}

生成器不仅生成模型,还生成端点。让我们快速看一下我们生成的内容:

public interface UserApiDelegate {

    default Optional<NativeWebRequest> getRequest() {
        return Optional.empty();
    }

    /**
     * POST /user : Create user
     * Create user functionality
     *
     * @param body Created user object (required)
     * @return successful operation (status code 200)
     * @see UserApicreateUser
     */

    default ResponseEntity<Void> createUser(User body) {
        return new ResponseEntity<>(HttpStatus.NOT_IMPLEMENTED);

    }
 
// ... omit deleteUser, getUserByName and updateUser
}

当然,生成器无法为我们生成我们的业务逻辑,但是它确实会UserApiDelegate为我们实现上述接口。
它还创建一个UserApi接口,将调用委托给UserApiDelegate:

@Validated
@Api(value = "user", description = "the user API")
public interface UserApi {

    default UserApiDelegate getDelegate() {
        return new UserApiDelegate() {};
    }

   
/**
     * POST /user : Create user
     * Create user functionality
     *
     * @param body Created user object (required)
     * @return successful operation (status code 200)
     */

    @ApiOperation(value =
"Create user"
      nickname =
"createUser"
      notes =
"Create user functionality"
      tags={
"user", })
    @ApiResponses(value = { 
        @ApiResponse(code = 200, message =
"successful operation") })
    @RequestMapping(value =
"/user",
        method = RequestMethod.POST)
    default ResponseEntity<Void> createUser(
      @ApiParam(value =
"Created user object" ,required=true )  
      @Valid 
      @RequestBody User body) {
        return getDelegate().createUser(body);
    }
    
   
// ... other methods omitted
}

生成器还为我们创建了一个Spring控制器,用于实现UserApi接口:

@javax.annotation.Generated(...)
@Controller
@RequestMapping("${openapi.reflectoring.base-path:/v2}")
public class UserApiController implements UserApi {

    private final UserApiDelegate delegate;

    public UserApiController(
      @Autowired(required = false) UserApiDelegate delegate) {
        this.delegate = Optional.ofNullable(delegate)
            .orElse(new UserApiDelegate() {});
    }

    @Override
    public UserApiDelegate getDelegate() {
        return delegate;
    }
}

如果Spring UserApiDelegate在应用程序上下文中找到我们的实现,它将注入到控制器的构造函数中。否则,将使用默认实现。
让我们启动应用程序并点击GET端点/v2/user/{username}。

curl -I http://localhost:8080/v2/user/Petros
HTTP/1.1 501
Content-Length: 0

但是为什么我们会收到501响应(未实现)?
因为我们没有实现UserApiDelegate接口,所以UserApiController使用了默认接口,该接口 返回HttpStatus.NOT_IMPLEMENTED。
现在让我们实现UserApiDelegate:

@Service
public class UserApiDelegateImpl implements UserApiDelegate {

    @Override
    public ResponseEntity<User> getUserByName(String username) {
        User user = new User();
        user.setId(123L);
        user.setFirstName("Petros");
        
       
// ... omit other initialization

        return ResponseEntity.ok(user);
    }
}

在类中添加@Service或@Component批注很重要,以便Spring可以将其拾取并注入到中UserApiController。
如果现在curl http://localhost:8080/v2/user/Petros再次运行,将收到有效的JSON响应:

{
  "id": 123,
 
"firstName": "Petros",
 
// ... omit other properties
}

我认为,使用Maven插件而不是Swagger Editor生成OpenAPI规范是更好的选择。那是因为我们对我们的选择有更多的控制权。该插件提供了一些配置和使用Git作为版本控制工具,我们可以放心地跟踪在任的任何变化pom.xml和openapi.yml。

可以在GitHub上浏览示例代码。