Netflix / Netflix/dgs-codegen

Generate interfaces for schema types defined in schema in kotlin projects

オープン
#653 コメント 3 件 リアクション 0 件 担当者 0 名 GitHub で見る

まだ誰も着手していません。

主要言語
Kotlin
スター
217
フォーク
116
PR マージ指標
30日以内にマージされた PR はありません

説明

When I'm trying to generateInterfaces for kotlin language with use of generateJava task, they are not generated.

I saw ticket here that touch on this topic and it was stated that it is probably a feature of not a first priority.

I could probably add some context to highlight that this is pretty vital thing to have in kotlin based projects.

Let's take a look at the following setup:

type Team {
    id: Int
    parentTeam: Team!
    childTeams: [Team!]!
}

Now I'm trying to organize my dataloaders and data fetchers as optimal as possible.
In kotlin projects I have the following options and none of them looks optimal to me:

  • Option 1: manually create interface for your types (not optimal from contract/schema perspective)

    • Create ITeam interface:
    interface ITeam {
        id: Int
        parentTeam: Team
        childTeams: [Team!]!
    }
    type Team implemets ITeam {
        id: Int
        parentTeam: ITeam!
        childTeams: [Team!]!
    }
    

    Then data fetcher will be something like this:

    @DgsData
    fun fetchParentTeam(
        dfe: DgsDataFetchingEnvironment
    ): CompletableFuture<Team> {
        val parentTeamId = dfe.getSource<Team>()?.parentTeam?.id
        return parentTeamId?.let {
            val loader: DataLoader<Long, Team> = dfe.getDataLoader(TeamDataLoader::class.java)
            loader.load(it)
        }
    }
    

    In case of relational database on val parentTeamId = dfe.getSource<Team>()?.parentTeam?.id line I have access to parent team id since it is ...toOne relation and I don't need additional db queries to find it out.

    Also in data loader I do this:

    @DgsDataLoader
    class TeamDataLoader(
        private val teamService: TeamService,
        private val gqlDtoBuilder: GQLDtoBuilder
    ) : MappedBatchLoader<Long, Team?> {
    
        override fun load(keys: MutableSet<Long>): CompletionStage<Map<Long, Team?>> {
            return CompletableFuture.supplyAsync {
                val results = teamService
                    .getTeamsByIds(keys)
                    .associate { it.id to gqlDtoBuilder.build(it) }
                    .toMutableMap<Long, Team?>()
    
                for (key in keys) {
                    results.putIfAbsent(key, null)
                }
    
                results
            }
        }
    }
    

    Looks good so far but the query for such a schema is not that concise as it could be:

    teams {
        parentTeam {
             id
             ... on Team {
                 parentTeam
                 // etc.
             }
        }
    }
    

    From client perspective it looks weird and doesn't feel right when performance backend stuff affects contract with external clients (another backend services, frontend, etc)

  • Option 2: not optimal from support perspective. We can use local context and it is good all in all but in a long team it looks a bit error prone since every time we do a fetch of Team type we need to remember to pass local context in all data/entity fetchers. If we don't do that we got an error. Moreover if we get an additional field in schema like teamLead we need to populate it everywhere we populate local context.
    If project's graphql contract is not 100% covered with tests it could lead to errors on test/prod environments and with graphql request variety it is pretty difficult to keep test coverage on these high levels.

  • Option 3: not optimal from performance perspective. We can fetch parentTeam with use of separate join db query and separate data loader logic but it is obviously not that fast and produces extra load on microservice itself and database.

  • Option 4: looks promising, but has some java<->kotlin interop issues. In this cause we have the same interfaces like in option 1 but clients don't see them and we can extend them the way we like and use to carry info required by fetchers.

  • Option 5: ideal option. Have interfaces generated for kotlin projects too. The same as option 4, but java<->kotlin interop boilerplate is not there.

Could you please share if there any plans to make generateInterfaces available for kotlin?

Thank you very much!

コントリビューションガイド

コントリビューションガイドを開く

はじめの一歩

  1. issue を最後まで読み、次にプロジェクトのコントリビューションガイドを読みます。
  2. 着手することを issue にコメントします — 二人が同じ作業をするのを防げます。
  3. リポジトリをフォークし、ブランチを切って変更します。
  4. issue 番号を参照したプルリクエストを送ります。

調査の方向性

まず、Kotlin プロジェクト向けの generateInterfaces タスクと generateJava タスクを追跡し、これまでのコンテキストについて関連する issue #200 を確認してください。Kotlin プロジェクトが generateInterfaces を通じてスキーマで定義された型のインターフェースを生成でき、その動作が適切なテストによって検証されれば完了です。

索引モデルが issue の本文から書いたものです。

評価

技術スタック
graphql, kotlin
領域
backend-api-design, tooling
issue の種類
機能追加
難易度
5/5
見積もり時間
1週間以上
活発さ
停滞
明瞭さ
説明が足りない
初心者へのやさしさ
30/100

新しい issue をメールで受け取る

初心者向けの GitHub issue を短くまとめたダイジェスト。