asken テックブログ

askenエンジニアが日々どんなことに取り組み、どんな「学び」を得ているか、よもやま話も織り交ぜつつ綴っていきます。 皆さまにも一緒に学びを楽しんでいただけたら幸いです! <br> 食事管理アプリ『あすけん』 について <br> https://www.asken.jp/ <br>

AI生成のOASからコード自動生成、作業時間とトークン削減の試み

はじめに

この記事は asken Advent Calender 2025 17日目の記事です。

こんにちはプロダクト開発部 法人事業担当の入江です。

先日OpenAPIの仕様書(以下、OAS)からコードを自動生成したので、その取り組みをご紹介します。

AIを活用した開発プロセス改善を考えていた際に、AIに書かせたOASをもとに後続の成果物を自動生成することで、AIのトークン消費を抑えつつプロセス改善できると考えたことがきっかけです。

OASのようなAPIの仕様書をもとにして開発を進めることを「スキーマ駆動開発」と呼びます。 これには多くの便利なツールが用意されていますが、これまでは「OpenAPIの仕様(記述ルール)を学習して正しく記載する」ということ自体に負担がかかるものでした。

しかし、生成AIを利用することでその負担が劇的に減ったことで、非常に取り入れやすいプロセスになったと考えています。

そこで今回は、OASをAIに書かせることを念頭に、OASからサーバーコードを生成することを試してみました。

OpenAPIで実現できること

成果物としてOASを作ることで、各種ツールを用いて、以下のようなことが自動で実行可能になります。

  • クライアントコードの自動生成(OpenAPI Generatorなど)
  • サーバーコードの自動生成(OpenAPI Generatorなど)
  • モックサーバーの起動(Prismなど)
  • プロパティベースドテストによる入出力仕様の自動検証(schemathesisなど)

今回は、「OASでサーバーコードを自動生成する」というフローに焦点を当てて紹介します。

この記事ではユーザの登録・変更・削除ができるAPIとしてAIに作ってもらったOASを利用することにします。

OASは以下のようなものであり、APIに関する情報やアクセス先のサーバ、APIのリクエスト/レスポンスに関する情報を含んでいるものです。

openapi: 3.0.3
info:
  title: User Management API
  description: ユーザーを管理するAPI
  version: 1.0.0

servers:
  - url: http://localhost:8080/api/v1
    description: ローカル開発サーバー

paths:
  /users:
    post:
      summary: ユーザー新規作成
      description: 新しいユーザーを作成します
      operationId: createUser
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/UserCreateRequest'
      responses:
        '201':
          description: ユーザー作成成功
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/User'
                
                
components:
  schemas:
    User:
      type: object
      description: ユーザー情報
      properties:
        user_id:
          type: string
          description: ユーザーID
          example: user001
        name:
          type: string
          description: 氏名
          example: 山田太郎
        birthdate:
          type: string
          format: date
          description: 生年月日
          example: '1990-01-15'
        height:
          type: number
          format: float
          description: 身長(cm)
          example: 170.5
        weight:
          type: number
          format: float
          description: 体重(kg)
          example: 65.0
      required:
        - user_id
        - name
        - birthdate
        - height
        - weight
              

一部抜粋

環境について

今回の構築した環境・ツール・OpenAPIバージョンは以下で確認しました。

  • SpringBoot: 4.0.0
  • kotlin 2.2
  • OpenAPI Generator: 7.17.0
  • OpenAPI: 3.0.3
  • Gradle: 8.14.3

1. まずはデフォルト設定で生成してみる

ツールをインストールします。今回は openapi-generator を使用します。

brew install openapi-generator

askenではKotlinを用いてSpring BootでWEBアプリケーションを開発しています。 まずは特に細かい設定をせず、以下のコマンドでコードを出力してみます。

openapi-generator generate  -i OpenAPI/user-api.yaml -g kotlin-spring -o ./sample1

実行すると、以下のようなログとともに大量のファイルが生成されました。

[main] INFO  o.o.codegen.TemplateManager - writing file .../sample1/src/main/kotlin/org/openapitools/model/Error.kt
[main] INFO  o.o.codegen.TemplateManager - writing file .../sample1/src/main/kotlin/org/openapitools/model/User.kt
[main] INFO  o.o.codegen.TemplateManager - writing file .../sample1/src/main/kotlin/org/openapitools/api/UsersApiController.kt
...
[main] INFO  o.o.codegen.TemplateManager - writing file .../sample1/build.gradle.kts
[main] INFO  o.o.codegen.TemplateManager - writing file .../sample1/src/main/kotlin/org/openapitools/Application.kt
...

出力内容の確認

大量に生成されたファイルについて中身を少し見ていきましょう。

ビルドツール関連

build.gradle.kts  
gradle/wrapper/gradle-wrapper.jar  
gradlew  
settings.gradle
pom.xml

Gradle関連のファイル(デフォルトで8.1.1を利用しているようです)に加え、 pom.xml も出力されています。「gradleとmavenとりあえず両方出力しておきます」ということでしょうか。

ソースコード関連

src/main/kotlin/org/openapitools/Application.kt  
src/main/kotlin/org/openapitools/HomeController.kt  
src/main/kotlin/org/openapitools/SpringDocConfiguration.kt  
src/main/kotlin/org/openapitools/api/ApiUtil.kt  
src/main/kotlin/org/openapitools/api/Exceptions.kt  
src/main/kotlin/org/openapitools/api/UsersApiController.kt  
src/main/kotlin/org/openapitools/model/Error.kt  
src/main/kotlin/org/openapitools/model/User.kt  
src/main/kotlin/org/openapitools/model/UserCreateRequest.kt  
src/main/kotlin/org/openapitools/model/UserUpdateRequest.kt

Springのエントリーポイントとなる Application.kt や設定ファイルに加え、OASをもとにしたControllerやModelが出力されています。

apiやmodelを見てみます。

@RestController
@Validated
@RequestMapping("\${api.base-path:/api/v1}")
class UsersApiController() {

    @Operation(
        summary = "ユーザー新規作成",
        operationId = "createUser",
        description = """新しいユーザーを作成します""",
        responses = [
            ApiResponse(responseCode = "201", description = "ユーザー作成成功", content = [Content(schema = Schema(implementation = User::class))]),
            ApiResponse(responseCode = "400", description = "リクエストが不正", content = [Content(schema = Schema(implementation = Error::class))]),
            ApiResponse(responseCode = "409", description = "ユーザーIDが既に存在", content = [Content(schema = Schema(implementation = Error::class))]) ]
    )
    @RequestMapping(
        method = [RequestMethod.POST],
        value = ["/users"],
        produces = ["application/json"],
        consumes = ["application/json"]
    )
    fun createUser(@Parameter(description = "", required = true) @Valid @RequestBody userCreateRequest: UserCreateRequest): ResponseEntity<User> {
        return ResponseEntity(HttpStatus.NOT_IMPLEMENTED)
    }
/**
 * ユーザー情報
 * @param userId ユーザーID
 * @param name 氏名
 * @param birthdate 生年月日
 * @param height 身長(cm)
 * @param weight 体重(kg)
 */
data class User(

    @Schema(example = "user001", required = true, description = "ユーザーID")
    @get:JsonProperty("user_id", required = true) val userId: kotlin.String,

    @Schema(example = "山田太郎", required = true, description = "氏名")
    @get:JsonProperty("name", required = true) val name: kotlin.String,

    @field:Valid
    @Schema(example = "Mon Jan 15 09:00:00 JST 1990", required = true, description = "生年月日")
    @get:JsonProperty("birthdate", required = true) val birthdate: java.time.LocalDate,

    @Schema(example = "170.5", required = true, description = "身長(cm)")
    @get:JsonProperty("height", required = true) val height: kotlin.Float,

    @Schema(example = "65.0", required = true, description = "体重(kg)")
    @get:JsonProperty("weight", required = true) val weight: kotlin.Float
) {

}

OASで定義したAPIの実装が出力されていそうです。

デフォルト生成の課題

デフォルト設定では、Spring Bootのプロジェクト構成(起動クラスやGradle設定など)そのものが丸ごと出力されてしまいます。しかし、プロジェクト構成自体はIntelliJ IDEAなどで作成した既存のものを使いたいケースが大半です。

また、Controllerクラス(UsersApiController.kt)が直接出力されていますが、このクラスに直接ロジックを実装してしまうと、OpenAPI定義を更新して再生成した際に実装が上書きされて消えてしまいます。 これでは運用に乗せられません。

欲しいものは 「APIインターフェース」および「Model」配下のクラスだけです。 また、自動生成される定義部分と、人間が書くロジック部分を分離する必要があります。

2. 生成される構成のチューニング

必要なファイルだけを生成し、ロジックを分離できるように設定を見直していきます。

Generatorの設定ファイルを作成

コマンドライン引数ですべて指定するのは大変なので、設定ファイル(generator_config.yaml)を作成します。

generatorName: kotlin-spring  
inputSpec: ./OpenAPI/user-api.yaml  # OASファイルのパス
outputDir: ./sample4  # 出力先(ここではSpringBootプロジェクトのルート)
apiPackage: inc.asken.example.presentation.api  
delegatePattern: true  
documentationProvider: none  
gradleBuildFile: false  
modelPackage: inc.asken.example.presentation.model  
skipDefaultInterface: true # このオプションを有効にしておくことでdelegatePattern利用時にdelegateApiにデフォルト実装が生成されるのを防げます  
useSpringBoot3: true

実行時はopenapi-generator generate -c ./generator_config.yaml のように指定します。

各設定の詳細は公式ドキュメントを参照して調整しましたが、ポイントは以下の通りです。

  • delegatePattern: true: これが重要です。Controllerの実装をDelegateインターフェースに委譲するパターンで生成します。これにより、自動生成ファイルと実装ファイルを分離できます。
  • skipDefaultInterface: true: delegatePatternがtrueの場合、このオプションを設定しておくことでApiDelegateにデフォルト実装が生成されるのを防げます。
  • gradleBuildFile: false: gradle関連のビルドファイルの生成を抑制します。

(mavenの設定ファイルpom.xmlを抑制するオプションがないようです。gradleファイル生成の抑制オプションはあるのに・・・)

生成ファイルの除外設定

設定ファイルだけでは抑制しきれないファイル(pom.xmlApplication.kt など)については、.openapi-generator-ignore ファイルを使って除外します。.gitignore と同じような記法で記述できます。

README.md  
pom.xml  
src/main/kotlin/org/openapitools/Application.kt  
src/main/resources/application.yaml

.openapi-generator-ignore (このファイルは出力先ディレクトリに配置する必要があります)

これで、既存のプロジェクトファイルを上書きせず、必要なAPI定義とModelだけを生成する準備が整いました。

3. Spring Bootプロジェクトへの組み込み

ここからは実際にIntelliJで作成したSpring Bootプロジェクトに対して、実際にGeneratorを用いてコードを生成します。

プロジェクトルートに先ほどの.openapi-generator-ignoreを配置し、以下のコマンドで生成を実行します。

openapi-generator generate -c ./generator_config.yaml

依存関係の追加

生成されたコードをビルドするには、バリデーション用のアノテーションなどを解決する必要があります。build.gradle.kts に以下のライブラリを追加しておきます。

implementation("org.springframework.boot:spring-boot-starter-validation")

Delegateの実装

delegatePattern: true を指定したことで全体として下記のようなクラス構成となっています。

※実際にはAPIごとにプレフィクスが付与されたクラスが生成されます。今回は以下のクラスが生成されています

API => UsersApi
APIController => UsersApiController
APIDelegateImpl => UsersApiDelegateImpl
model => User, UserCreateRequest, UserUpdateRequest

まずControllerがシンプルなものに置き換わっています。

@RestController
@Validated
interface UsersApi {

    fun getDelegate(): UsersApiDelegate

    @RequestMapping(
            method = [RequestMethod.POST],
            value = ["/users"],
            produces = ["application/json"],
            consumes = ["application/json"]
    )
    fun createUser( @Valid @RequestBody userCreateRequest: UserCreateRequest): ResponseEntity<User> {
        return getDelegate().createUser(userCreateRequest)
    }
@jakarta.annotation.Generated(value = ["org.openapitools.codegen.languages.KotlinSpringServerCodegen"], comments = "Generator version: 7.17.0")
@Controller
@RequestMapping("\${openapi.userManagement.base-path:/api/v1}")
class UsersApiController(
        private val delegate: UsersApiDelegate
) : UsersApi {

    override fun getDelegate(): UsersApiDelegate = delegate
}

UsersApiDelegateUsersApiControllerにDIされるようになり、Controllerが受け取ったリクエストのパラメータをdelegateの各メソッドに渡すような構成に変化しています。

あとは自動生成された UsersApiDelegate インターフェースを実装するクラス(UsersApiDelegateImpl)を自分で作成し、そこにビジネスロジックを記述します。

@Component  
class UsersApiDelegateImpl : UsersApiDelegate {  
   /** 
    * @see UsersApi#createUser  
    */
    override fun createUser(userCreateRequest: UserCreateRequest): ResponseEntity<User> {  
        // ここに実際の実装を書く
        return ResponseEntity.ok(  
            User(  
                userId = userCreateRequest.userId,  
                name = userCreateRequest.name,  
                birthdate = userCreateRequest.birthdate,  
                height = userCreateRequest.height,  
                weight = userCreateRequest.weight,  
            )  
        )  
    }
}

このように実装クラスを分けておくことで、今後API仕様が変わってコードを再生成しても、自動生成されるインターフェースだけが更新され、この実装クラスは上書きされずに残ります。 コンパイルエラーになった箇所だけを修正すれば良いので、保守性が向上すると考えています。

起動と動作確認

最後にOpenAPIのドキュメント(Swagger UIなど)からリクエストを送り、正しく動作することを確認して完了です。

下記は実際に起動したSpringのログです。

2025-12-15T14:16:16.202+09:00  INFO 35442 --- [sample4] [           main] inc.asken.sample4.Sample4ApplicationKt   : Starting Sample4ApplicationKt using Java 17.0.11 with PID 35442 (open_api_sample/sample4/build/classes/kotlin/main started )
2025-12-15T14:16:16.203+09:00  INFO 35442 --- [sample4] [           main] inc.asken.sample4.Sample4ApplicationKt   : No active profile set, falling back to 1 default profile: "default"
2025-12-15T14:16:16.492+09:00  INFO 35442 --- [sample4] [           main] o.s.boot.tomcat.TomcatWebServer          : Tomcat initialized with port 8080 (http)
2025-12-15T14:16:16.497+09:00  INFO 35442 --- [sample4] [           main] o.apache.catalina.core.StandardService   : Starting service [Tomcat]
2025-12-15T14:16:16.497+09:00  INFO 35442 --- [sample4] [           main] o.apache.catalina.core.StandardEngine    : Starting Servlet engine: [Apache Tomcat/11.0.14]
2025-12-15T14:16:16.522+09:00  INFO 35442 --- [sample4] [           main] b.w.c.s.WebApplicationContextInitializer : Root WebApplicationContext: initialization completed in 301 ms
2025-12-15T14:16:16.733+09:00  INFO 35442 --- [sample4] [           main] o.s.boot.tomcat.TomcatWebServer          : Tomcat started on port 8080 (http) with context path '/'
2025-12-15T14:16:16.735+09:00  INFO 35442 --- [sample4] [           main] inc.asken.sample4.Sample4ApplicationKt   : Started Sample4ApplicationKt in 0.665 seconds (process running for 0.88)

無事に起動しました。最後にリクエストを送ってみます。

OASを作成しておけば、Swagger UIで直接リクエストすることも可能になります

うまく動作しました。

まとめ

設定については、公式ページを参照しても挙動がどのように変化するかが難解な点もありますが、一度 configignore ファイルを設定してしまえば、あとは以下のサイクルを回すだけで開発が進められます。

  1. AIにOpenAPIの仕様変更を依頼する
  2. OpenAPI-Generatorコマンドでコードを再生成する
  3. Delegateクラスの実装を修正する

OpenAPI Generatorには kotlin-spring の他にも多様なコード生成機能があり、ここでは紹介しきれないほどです。

今回はOpenAPIをAIに記載させて、その成果物をもとにコードを生成する取り組みを紹介しました。

他にも、生成したOpenAPIをもとにモックサーバーを立ち上げたり、テストコードを生成したりといった活用も期待できます。

今までスキーマ駆動開発の導入を、学習コストや運用コスト(仕様書の記述が大変…)の都合で見送っていた方々も、「仕様書作成はAIに任せる」 という前提で検討してみることで、より効率的で新しい開発体験ができるのではないかと考えています。

採用について

askenではエンジニアを絶賛募集中です。

まずはカジュアルにお話しできればと思いますので、ぜひお気軽にご連絡ください!

https://hrmos.co/pages/asken/jobs

asken techのXアカウントで、askenのテックブログやイベント情報など、エンジニアリングに関する最新情報を発信していますので、ぜひフォローをお願いします!