使用Spring Boot和Swagger進行API優先開發 - reflectoring.io

banq發表於2020-03-24

遵循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 UserApi#createUser
     */
    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上瀏覽示例程式碼。

相關文章