Share
Embed
Contract First или страх и ненависть в королевстве CodeGen
Часто для решения одной бизнес задачи требуется коммуникация и совместная разработка нескольких команд. В микросервисной архитектуре задача упрощается, ведь сервисы имеют разную кодовую базу и взаимодействуют друг с другом по API. А значит требуется лишь договориться об общем API в начале разработки. Но так ли все просто и как автоматизировать этот процесс рассмотрим в нашем докладе.
Romanov Alexey
November 02, 2026
Transcript
36 slides
-
Contract First или страх и ненависть в королевстве CodeGen Романов
А., ALM 2.0 Платформа -
СОДЕРЖАНИЕ + Коммуникация между командами разработки при совместной работе над
одной задачей. + Что такое контракт? OpenAPI. + Contract First vs. Code First. Плюсы и минусы подходов. + Рассматриваем что умеет проект OpenAPI Generator. + Генерируем код по контракту на клиенте и сервере, разбираем что получилось. + Убираем лишние файлы. + Используем свои шаблоны генерации кода по OpenAPI. + Вместо выводов: как поддержать баланс между чувством прекрасного и сгенерированным кодом? -
О СПИКЕРЕ • В индустрии более 13 лет • Основная
специализация – Java / Kotlin • Работаю в Иннотехе в роли Software Архитектора уже 3.5 года • Мы с вами встречались на других конференциях 🙂 3 -
КОММУНИКАЦИЯ МЕЖДУ КОМАНДАМИ ПРИ СОВМЕСТНОЙ РАБОТЕ НАД ЗАДАЧЕЙ В Микросервисной
Архитектуре практически любая задача требует взаимодействия нескольких сервисов, а значит для ее выполнения требуется параллельно вести разработку в нескольких командах. 4 -
ЧТО ТАКОЕ КОНТРАКТ? Контракт – это соглашение о том, как
будет выглядеть наше API, и какие параметры оно будет принимать. Спецификация OpenAPI определяет стандарт независимого от языка описания API, который позволяет понимать возможности службы без доступа к исходному коду. По сути, OpenAPI — это описание методов API, для которых описываются заголовки, входные и выходные параметры. 5 -
OPENAPI 1. openapi: 3.0.1 2. info: 3. title: Servers API
4. version: v1 5. servers: 6. - url: http://localhost:8080 7. paths: 8. /api/v1/servers: 9. get: 10. tags: 11. - server 12. summary: Find all servers 13. operationId: all 14. responses: 15. '200': 16. description: OK 17. content: 18. application/json: 19. schema: 20. $ref: '#/components/schemas/ServersResponse' 6 -
CONTRACT FIRST VS. CODE FIRST У нас есть два источника
правды: + Код + Контракт 8 -
CODE FIRST Code First – сначала пишем код, по нему
генерируем контакт. + В коде не все можно описать, следовательно, мы можем получить невалидный или неполный OpenAPI. + Требуется время для написания кода, следовательно, другие команды вынуждены нас ждать. + Есть проблемы при генерации методов со сходной сигнатурой (/api/v1/users и /api/v1/users?login={{login}}) – они сливаются в один метод. 9 -
CONTRACT FIRST Contract First – мы сначала согласовываем OpenAPI контракт,
а потом по нему пишем или генерируем код. 10 -
CONTRACT FIRST Contract First – у нас есть согласованный со
всеми сторонами контакт, по нему мы генерируем код. + Сгенерированный код рабочий, но проблема в том, что он выглядит очень плохо и работать с ним зачастую неудобно. + В команде есть Code Style, соглашения о названиях классов и т.п., а значит нужно, чтобы весь код в проекте удовлетворял этим критериям. 11 -
OPENAPI GENERATOR Для генерации кода возьмем самое распространенное решение –
проект OpenAPI Generator. + Генерация клиентского и серверного кода для всем популярных языков и фреймворков (Java, Kotlin, Go, C#, JavaScript и т.п.). + Валидация OpenAPI. + Возможность кастомизации шаблонов. https://openapi-generator.tech/ 12 -
OPENAPI 1. openapi: 3.0.1 2. info: 3. title: Servers API
4. version: v1 5. servers: 6. - url: http://localhost:8080 7. paths: 8. /api/v1/servers: 9. get: 10. tags: 11. - server 12. summary: Find all servers 13. operationId: all 14. responses: 15. '200': 16. description: OK 17. content: 18. application/json: 19. schema: 20. $ref: '#/components/schemas/ServersResponse' 14 -
ГЕНЕРИРУЕМ КОД КЛИЕНТА $ openapi-generator-cli generate \ -g kotlin \
--api-package ru.romanow.openapi.client.rest \ --model-package ru.romanow.openapi.client.models \ --additional-properties=enumPropertyNaming=UPPERCASE \ -o client/build/generated \ -i openapi/servers.yml 15 -
ГЕНЕРИРУЕМ КОД КЛИЕНТА 1. /** 2. * Please note: 3.
* This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). 4. * Do not edit this file manually. 5. */ 6. @file:Suppress("ArrayInDataClass", "EnumEntryName", "UnusedImport") 7. package ru.romanow.openapi.client.models 8. 9. import ru.romanow.openapi.client.models.Purpose 10.import ru.romanow.openapi.client.models.StateInfo 11.import com.fasterxml.jackson.annotation.JsonProperty 12. 13.data class ServerResponse ( 14. @get:JsonProperty("id") val id: kotlin.Int, 15. @get:JsonProperty("purpose") val purpose: Purpose, 16. @get:JsonProperty("latency") val latency: kotlin.Int, 17. @get:JsonProperty("bandwidth") val bandwidth: kotlin.Int, 18. @get:JsonProperty("state") val state: StateInfo 19.) 17 -
УБИРАЕМ ЛИШНИЕ ФАЙЛЫ В сгенерированном коде мы получаем большое количество
лишних файлов: + build.gradle, settings.gradle, gradlew, gradlew.bat, gradle/*; + README.md, docs/*; + файлы в пакете /org/.../infrastructure/**. 18 -
УБИРАЕМ ЛИШНИЕ ФАЙЛЫ Опишем эти исключения в файле .openapi-generator-ignore: +
**/*.gradle + **/gradlew* + **/README.md + **/docs/*.md + **/gradle/ + **/.openapi-generator-ignore + **/org/openapitools/client/infrastructure/*.kt + **/ru/romanow/openapi/client/rest/*.kt 19 -
УБИРАЕМ ЛИШНИЕ ФАЙЛЫ $ openapi-generator-cli generate \ -g kotlin \
--config openapi/client/config.yml \ --ignore-file client/.openapi-generator-ignore \ -o client/build/generated \ -i openapi/servers.yml 20 -
СТРУКТУРА ШАБЛОНА В качестве движка шаблонизации используется Mustache. Выгрузим шаблон
для Kotlin: $ openapi-generator-cli author \ template -g kotlin \ --library jvm-spring-webclient \ -o openapi/templates 22 -
СТРУКТУРА ШАБЛОНА Есть 5 входных типов файлов, а остальные просто
являются частью других шаблонов: + API – клиент или сервер; + APIDocs – markdown описание API; + Model – модели; + ModelDocs – markdown описание моделей; + SupportingFiles – дополнительные файлы. 24 -
МОДИФИЦИРУЕМ ШАБЛОН { "importPath": "ru...models.CreateServerRequest", "model": { "name": "CreateServerRequest", "classname":
"CreateServerRequest", "isPrimitiveType": false, "vars": [ { "openApiType": "string", "dataType": "kotlin.String", "name": "purpose", "required": true, "isPrimitiveType": true, "isString": true, "isNumeric": false, "isNullable": false, "vars": [], "hasValidation": false, } ] } 1. data class {{classname}} ( 2. {{#vars}} 3. var {{name}}: {{{dataType}}}? = null 4. {{/vars}} 5. ) } 25 -
МОДИФИЦИРУЕМ ШАБЛОН 1. 2. 3. 4. 5. 6. 7. 8.
9. {{>licenseInfo}} package {{package}}; {{#models}} {{#model}} {{#isEnum}}{{>modelEnum}}{{/isEnum}}{{^isEnum}}{{>pojo}}{{/isEnum}} {{/model}} {{/models}} 26 -
МОДИФИЦИРУЕМ ШАБЛОН + {{>...}} – подключение шаблона. + {{ …
}} – обращение к значению переменной; + {{#...}} .... {{\...}} – обращение к переменной (так же используется для обхода списка). 27 -
МОДИФИЦИРУЕМ ШАБЛОН $ openapi-generator generate \ -g kotlin \ --config
openapi/client/config.yml \ --ignore-file client/.openapi-generator-ignore \ --template-dir openapi/client/templates \ -o client/build/generated \ -i openapi/servers.yml 29 -
МОДИФИЦИРУЕМ ШАБЛОН + Если нам не требуется модифицировать шаблон, то
мы просто его не меняем и он создается на основе базовых шаблонов. + Если требуется убрать сгенерированные файлы, то просто описываем их в .openapi-generator-ignore. 31 -
ВЫВОДЫ + Если разработка идет в одной команде, то возможно
Code First вам подойдет, т.к. для него не требуется никаких особенных настроек. + Возможность генерировать модели и клиента по OpenAPI позволяет нам быстрее реагировать на изменения в реализации. + OpenAPI generator сгенерирует за вас весь boilerplate. + OpenAPI generator позволяет кастомизировать шаблоны, следовательно, вы можете сгенерировать код, удовлетворяющий Code Style и вашему чувству прекрасного. 32