UGA Boxxx

つぶやきの延長のつもりで、知ったこと思ったこと書いてます

【Spring Boot】Apollo3が利用できなかった話

Spring Boot にApollo Clientを導入しようとした際に、Apolloのバージョンがv2系とv3系があることがわかり、当初v3系にしようとしていたがうまくいかなかった

uga-box.hatenablog.com

前提としてSpring Boot は2.5.12を使っていて、古いのは承知しているが今すぐバージョンを上げられないため、このバージョンで動くようにする必要がある

うまくいかなった理由は、Rx3Apollo の部分でランタイム エラーが発生したこと

同じ事象のissueが上がっていたので読むと
github.com

これはspring boot のバージョンと中で使われている kotlinx-coroutines-* のバージョンがアンマッチが原因とわかった

kotlinx-coroutines-* のバージョンが1.6.1である必要があるが、以下をみると確かに<kotlin-coroutines.version>1.5.2</kotlin-coroutines.version>が使われている

https://repo1.maven.org/maven2/org/springframework/boot/spring-boot-dependencies/2.5.12/spring-boot-dependencies-2.5.12.pom

ということで、spring-bootのバージョンを上げられないので、Apollo Kotlin v2を使うことにした

【Spring Boot】GraphQL Apollo Client でリクエスト時にヘッダーにAuth情報を含めたい

以前セットアップしたApollo Client でリクエスト時にヘッダーにAuth情報を含めたい

uga-box.hatenablog.com

設定方法は以下に書かれている

10. Authenticate your queries - Apollo GraphQL Docs

Kotlin用をJavaに読み替えて、以下のように書くけば良さそう

    OkHttpClient okHttp =
        new OkHttpClient.Builder()
            .addInterceptor(
                chain -> {
                  Request request =
                      chain
                          .request()
                          .newBuilder()
                          .method("POST", chain.request().body())
                          .addHeader("X-Api-Key", properties.getAuth().getKey())
                          .build();
                  return chain.proceed(request);
                })
            .build();

    ApolloClient client = ApolloClient.builder()
        .serverUrl("https://apollo-fullstack-tutorial.herokuapp.com/")
        .okHttpClient(okHttp)
        .build();

Spring boot 2.5系の場合エラーになる

上の実装をして実行したところ、以下のランタイムエラーが発生した

java.lang.NoSuchFieldError: Companion

これはcom.squareup.okhttp3:okhttp:4.xが期待される所でcom.squareup.okhttp3:okhttp:3.x が利用されたということで発生するエラー

下を見ると、確かにspring-bootのv2.5系のokhttpはokhttp3がv3系なので、これが原因だった

https://repo1.maven.org/maven2/org/springframework/boot/spring-boot-dependencies/2.5.12/spring-boot-dependencies-2.5.12.pom

pomに直接okhttp3をv4系にあげてみるとエラーが発生しなくなった

動くようにはなったが、spring-bootが古いのはよくない状態なので、一旦はこれで様子を見てspring-bootのバージョンアップを検討する

参考

https://wrongwrong163377.hatenablog.com/entry/2022/01/19/192535

【Spring Boot】Spring Boot でGraphQL Apolloクライアントを使う

Spring Boot でGraphQLで構築された外部のAPIを利用するため、GraphQLクライアントを用意する

GraphQLクライアントには Apollo や Relay が思いつくが、RelayはどちらかというとReact特化でApolloのようにさまざまな言語で使えるようになっていないとのこと

参考記事
最高のGraphQLクライアントを求めて Apollo vs Relay - nakamoriのblog

そのため Apollo でGraphQLクライアントをセットアップすることにした

セットアップ

Spring Boot のバージョンは 2.5.12(理由があって上げられない)、Apollo クライアントは v2系を使う(理由は別記事に書く)

以下の公式ドキュメントと
4. Execute your first query - Apollo GraphQL Docs

以下の記事が大変参考になったのでこれらを見ながら進める
Java × Spring Boot × Apollo で GraphQL クライアントを実装する #Java - Qiita

pom.xml に記載

まず、pom.xml に必要とするライブラリ記載する

この時、同期通信を簡単に書くために RxJava で書けるライブラリも記載する

    <dependency>
      <groupId>com.apollographql.apollo</groupId>
      <artifactId>apollo-runtime</artifactId>
      <version>2.5.14</version>
    </dependency>

    <dependency>
      <groupId>com.apollographql.apollo</groupId>
      <artifactId>apollo-rx3-support</artifactId>
      <version>2.5.14</version>
    </dependency>

また、Apollo による Query モデルクラスの自動生成を行うため、以下のプラグインも記載する

GitHub - aoudiamoncef/apollo-client-maven-plugin at support/apollo-2

<plugins>
    <plugin>
        <groupId>com.github.aoudiamoncef</groupId>
        <artifactId>apollo-client-maven-plugin</artifactId>
        <version>4.0.6</version>
        <executions>
            <execution>
                <goals>
                    <goal>generate</goal>
                </goals>
                <configuration>
                    <services>
                        <example-api>
                            <compilationUnit>
                                <name>example</name>
                                <compilerParams>
                                    <rootPackageName>com.example.graphql.client</rootPackageName>
                                </compilerParams>
                            </compilationUnit>
                        </example-api>
                    </services>
                </configuration>
            </execution>
        </executions>
    </plugin>
</plugins>

Query モデルクラスの自動生成

先ほど入れたpluginのintrospectionの箇所に、利用する外部APIの情報や出力先を記載する

mvn コマンドで generate-sourcesを実行すると、src/main/graphql/exampleに schema.json が生成されている

さらに、その同フォルダに GraphQLクエリファイルを置き、再度プラグインを実行すると target 配下に Query モデルクラスが自動生成されているので、このフォルダにクラスパスを通しておく

クライアントインスタンスの生成

以下のようにApolloクライアントのインスタンスを生成する

final ApolloClient apolloClient = ApolloClient.builder()
        .serverUrl("http://localhost:5000/graphql/endpoint")
        .build();

このクライアントと先ほど生成したクエリを使って実行する

        MyQuery query = MyQuery.builder().build();

         ApolloQueryCall<Data> apolloQueryCall = apolloClient.query(query);

        return Rx3Apollo.from(apolloQueryCall)
                .map(Response::getData)
                .map(MyQuery.Data::getOffers)
                .blockingFirst();

これで一旦、単純なセットアップは完了した

リクエスト時にヘッダーにAuth情報を含めたい場合はどうするかなど、詰まったところは別記事にする

ApolloクライアントをつかないGraphQLクライアントのセットアップ

試していないが spring-boot-starter-graphqlでGraphQLクライアントをセットアップすることも可能みたい

www.danvega.dev

<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-graphql</artifactId>
</dependency>

簡単に始められそうだが、スキーマ情報はない中でクエリを書くみたいで後々苦労しそう?

【Apollo Client 】Apollo Client Maven Plugin でコードの自動生成

Apollo Client をSpring Boot を使う際に、introspection を利用したコードの自動生成を行いたい

Maven Plugin があるのでこれを利用する(Apollo v2用)
github.com

https://mvnrepository.com/artifact/com.github.aoudiamoncef/apollo-client-maven-plugin

schema.jsonがある場合は src/main/graphqlにそれを配置した上で、pom.xml ファイルに以下のように記述する

      <plugin>
        <groupId>com.github.aoudiamoncef</groupId>
        <artifactId>apollo-client-maven-plugin</artifactId>
        <version>4.0.6</version>
        <executions>
          <execution>
            <goals>
              <goal>generate</goal>
            </goals>
            <configuration>
              <services>
                <example-api>
                  <compilationUnit>
                    <name>example</name>
                    <compilerParams>
                      <rootPackageName>com.example.graphql.client</rootPackageName>
                    </compilerParams>
                  </compilationUnit>
                </example-api>
              </services>
            </configuration>
          </execution>
        </executions>
      </plugin>

schema.jsonがない場合は、以下のようにintrospection.enable.trueにしてオプションを記述すると introspection を利用して自動で作成してくれる

      <plugin>
        <groupId>com.github.aoudiamoncef</groupId>
        <artifactId>apollo-client-maven-plugin</artifactId>
        <version>4.0.6</version>
        <executions>
          <execution>
            <goals>
              <goal>generate</goal>
            </goals>
            <configuration>
              <services>
                <example-api>
                  <compilationUnit>
                    <name>example</name>
                    <compilerParams>
                      <rootPackageName>com.example.graphql.client</rootPackageName>
                    </compilerParams>
                  </compilationUnit>
                  <introspection>
                    <enabled>true</enabled>
                    <endpointUrl>com.endpoint.url</endpointUrl>
                    <schemaFile>${project.basedir}/src/main/graphql/schema.json</schemaFile>
                  </introspection>
                </example-api>
              </services>
            </configuration>
          </execution>
        </executions>
      </plugin>

ヘッダーに認証情報を含めたい場合

introspection のクエリ実行時にヘッダーに認証情報を含めたい場合は<headers></headers>に記載する

  <introspection>
    ...
    <headers>
       <X-My-Api-Key>my-api-token</X-My-Api-Key>
    </headers>
  </introspection>

ドキュメントには<headers></headers>しか書いてなくて、中をどうやって書けばいいか調査に苦労したが上のやり方で行けた

これでschema.json が自動生成され、src/main/graphqlにクエリも置いておけば、Javaコードも生成される

【GraphQL】IntrospectionでGraphQLスキーマがどのようなクエリをサポートしているのか知る

APIをGraphQLで構築している外部サービスを利用することになった

提供されたドキュメントには利用できるクエリや型の情報が載っているが、使うとなった時にドキュメントから読み取ってスキーマを作るのは辛いと感じた

なのでドキュメントからではなくGraphQLスキーマが実際どのようなクエリをサポートしているのか知る方法を調べた

Introspectionを使う

GraphQLには Introspection という機能があることを知った

https://graphql.org/learn/introspection/

https://spec.graphql.org/October2021/#sec-Schema-Introspection

Introspection はGraphQLサーバーで利用される引数、フィールド、型、description、型の非推奨ステータスなどのデータ構造に関する情報を教えてくれるというもの

{
  __schema {
    types {
      name
    }
  }
}

自身が Introspection を利用するのもいいが、それを使ってschema.jsonを生成し、それを基に利用したいプログラミング言語の型にしてくれるツールがあるので、それを利用するのが良さそう

例えば、Apollo でのコード生成ツールなどを利用するのが良さそう

github.com

【Spring Boot】AutoConfiguration おさらい

何気なく使っていた Spring Boot の AutoConfiguration をおさらいする

https://docs.spring.io/spring-boot/docs/2.5.x/reference/html/using.html#using.auto-configuration

AutoConfigurationとは自分でBean実装をしなくても、 jarの依存関係に基づいて Spring アプリケーションを自動的に構成する機能

AutoConfigure用のBean定義ファイル(コンフィギュレーションクラス)がインポートされ、インポートされたコンフィギュレーションクラスの定義に従ってBean定義が行われる

AutoConfigureの有効化

SpringApplication.run()の第一引数に@SpringBootApplicationアノテーションがついたコンフィギュレーションクラスを渡すか、@EnableAutoConfigurationアノテーションがついたコンフィギュレーションクラスを渡す

コンフィギュレーションクラスとは@Configurationアノテーションがついたクラスのことだが、クラスに直接付与されていなくても付与したアノテーションの内部のどこかで付与されていればよい

こうすることでエントリポイントとして起動時にスキャンし、必要なBeanの設定が行われる

適用順序

他のコンフィギュレーションクラスと設定順序に依存がある場合は

  • @AutoConfigureAfter
  • @AutoConfigureBefore
  • @AutoConfigureOrder

を使用する

ある条件の時だけ有効化する

Bean設定しておいたものを、ある条件の時だけ有効化したい

その時は@Conditionalaアノテーションを使う

https://docs.spring.io/spring-boot/docs/2.5.x/reference/htmlsingle/#features.developing-auto-configuration.condition-annotations

例えば、指定したクラスがクラスパス上に存在する場合に適用したい場合は@ConditionalOnClassを使うなど

種類が多いのでこれは別の機会にまとめる

Spring Conditional Annotations | Baeldung

無効化

AutoConfigureを有効化すると、Spring Bootが用意していたAutoConfigure用のコンフィギュレーションクラスがインポートされる

その中でもし無効化したものがあれば以下のようにすることで無効化される

@SpringBootApplication(exclude = { DataSourceAutoConfiguration.class })
public class MyApplication {

}

これは以下のように application.propertiesファイルに記述するでも良い

spring.autoconfigure.exclude=org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration

依存注入

Bean設定されたコンフィギュレーションクラスは、コンストラクターインジェクションにより依存関係が解決された状態でコンストラクターで呼び出すことが可能になる

例えば、RiskAssessorがBean登録されている場合、以下のようなサービスクラスで RiskAssessor がコンストラクタで呼び出すことができる

@Service
public class MyAccountService implements AccountService {

    private final RiskAssessor riskAssessor;

    public MyAccountService(RiskAssessor riskAssessor) {
        this.riskAssessor = riskAssessor;
    }

    // ...

}

参考

Spring BootのAutoConfigureの仕組みを理解する #Java - Qiita

【date-fns】v3 がリリースされてた

去年の12月に date-fns のv3がリリースされた

blog.date-fns.org

内容を確認してみると以下とのこと

  • TypeScript に完全に書き換えた
  • 実行時の型チェックが不要になった
  • 最小サイズは 200 バイトになった
  • 文字列引数が戻ってきた
  • UTCDateなどの Date クラス拡張機能のサポート
  • Node.js ESM サポート
  • デフォルトのエクスポートは不要
  • ESM/Deno の DX を改善する新しいフラット ライブラリ構造
  • IE サポート終了

バイトサイズが小さくなるのはうれしい

デフォルトのエクスポートは不要とあるが、Next.jsでは動かなくなるみたい?なので、Next.jsユーザーはデフォルトのエクスポートで使うみたい