契約をプロデューサーに保存する代わりに、契約で共通リポジトリを使用するにはどうすればよいですか ?

契約書をプロデューサーと一緒に保管するのではなく、契約書を共通の場所に保管するもう 1 つの方法があります。この状況は、セキュリティの課題 (コンシューマーがプロデューサーのコードを複製できない場合) に関連している可能性があります。また、契約を 1 か所にまとめておけば、プロデューサーとして、コンシューマーが何人いるのか、ローカルの変更でどのコンシューマーが破棄される可能性があるのかがわかります。

リポジトリ構造

座標 com.example:server を持つプロデューサーと 3 つのコンシューマー client1client2client3 があると仮定します。次に、共通の契約を含むリポジトリで、次のセットアップを行うことができます (Spring Cloud Contract のリポジトリ [GitHub] (英語)  samples/standalone/contracts サブフォルダーでチェックアウトできます)。次のリストは、そのような構造を示しています。

├── com
│   └── example
│       └── server
│           ├── client1
│           │   └── expectation.groovy
│           ├── client2
│           │   └── expectation.groovy
│           ├── client3
│           │   └── expectation.groovy
│           └── pom.xml
├── mvnw
├── mvnw.cmd
├── pom.xml
└── src
    └── assembly
        └── contracts.xml

スラッシュ区切りの groupid/artifact id フォルダー (com/example/server) には、3 つのコンシューマー (client1client2client3) が期待されています。このドキュメント全体に従って、期待されるのは標準の Groovy DSL 契約ファイルです。このリポジトリは、リポジトリのコンテンツに 1 対 1 でマップする JAR ファイルを生成する必要があります。

次の例は、server フォルダー内の pom.xml ファイルを示しています。

Spring Cloud Contract Maven プラグイン以外には依存関係はありません。これらの pom.xml ファイルは、コンシューマー側で mvn clean install -DskipTests を実行してプロデューサープロジェクトのスタブをローカルにインストールするために必要です。

ルートフォルダー内の pom.xml ファイルは次のようになります。

アセンブリプラグインを使用して、すべての契約を含む JAR を構築します。次の例は、そのようなセットアップを示しています。

ワークフロー

このワークフローでは、Spring Cloud Contract がコンシューマー側とプロデューサー側の両方でセットアップされていることを前提としています。契約との共通リポジトリには、適切なプラグインのセットアップもあります。CI ジョブは、すべての契約のアーティファクトを構築し、Nexus または Artifactory にアップロードするための共通リポジトリ用に設定されています。次の図は、このワークフローの UML を示しています。

how-to-common-repo

コンシューマー

コンシューマーがプロデューサーコードを複製する代わりに、契約をオフラインで作業したい場合、コンシューマーチームは共通リポジトリを複製し、必要なプロデューサーのフォルダー ( com/example/server など) に移動し、mvn clean install -DskipTests を実行して契約から変換されたスタブをローカルにインストールします。

プロデューサー

プロデューサーは、次のように、Spring Cloud Contract Verifier を変更して、契約を含む JAR の URL と依存関係を提供できます。

<plugin>
	<groupId>org.springframework.cloud</groupId>
	<artifactId>spring-cloud-contract-maven-plugin</artifactId>
	<configuration>
		<contractsMode>REMOTE</contractsMode>
		<contractsRepositoryUrl>
			https://link/to/your/nexus/or/artifactory/or/sth
		</contractsRepositoryUrl>
		<contractDependency>
			<groupId>com.example.standalone</groupId>
			<artifactId>contracts</artifactId>
		</contractDependency>
	</configuration>
</plugin>

この設定では、groupid または com.example.standalone および artifactid または contracts を含む JAR が link/to/your/nexus/or/artifactory/or/sth (英語) からダウンロードされます。次に、ローカルの一時フォルダーで解凍され、com/example/server に存在する契約が、テストとスタブの生成に使用される契約として選択されます。この規則により、プロデューサーチームは、互換性のない変更が行われたときにどのコンシューマーチームが壊れているかを知ることができます。

残りの流れは同じように見えます。

プロデューサーごとではなくトピックごとにメッセージング契約を定義するにはどうすればよいですか ?

共通リポジトリでのメッセージング契約の重複を避けるために、少数のプロデューサーが 1 つのトピックにメッセージを書き込む場合、REST 契約がプロデューサーごとのフォルダーに配置され、メッセージング契約がトピックごとのフォルダーに配置される構造を作成できます。

Maven プロジェクトの場合

プロデューサー側で作業できるようにするには、関心のあるトピックをメッセージングすることによって共通リポジトリ jar ファイルをフィルタリングするための包含パターンを指定する必要があります。Maven Spring Cloud Contract プラグインの includedFiles プロパティを使用すると、これが可能になります。また、デフォルトのパスは共通リポジトリ groupid/artifactid となるため、contractsPath を指定する必要があります。次の例は、Spring Cloud Contract 用の Maven プラグインを示しています。

<plugin>
   <groupId>org.springframework.cloud</groupId>
   <artifactId>spring-cloud-contract-maven-plugin</artifactId>
   <version>${spring-cloud-contract.version}</version>
   <configuration>
      <contractsMode>REMOTE</contractsMode>
      <contractsRepositoryUrl>https://link/to/your/nexus/or/artifactory/or/sth</contractsRepositoryUrl>
      <contractDependency>
         <groupId>com.example</groupId>
         <artifactId>common-repo-with-contracts</artifactId>
         <version>+</version>
      </contractDependency>
      <contractsPath>/</contractsPath>
      <baseClassMappings>
         <baseClassMapping>
            <contractPackageRegex>.*messaging.*</contractPackageRegex>
            <baseClassFQN>com.example.services.MessagingBase</baseClassFQN>
         </baseClassMapping>
         <baseClassMapping>
            <contractPackageRegex>.*rest.*</contractPackageRegex>
            <baseClassFQN>com.example.services.TestBase</baseClassFQN>
         </baseClassMapping>
      </baseClassMappings>
      <includedFiles>
         <includedFile>**/${project.artifactId}/**</includedFile>
         <includedFile>**/${first-topic}/**</includedFile>
         <includedFile>**/${second-topic}/**</includedFile>
      </includedFiles>
   </configuration>
</plugin>
前述の Maven プラグインの値の多くは変更できます。「典型的な」例を提供しようとするのではなく、説明の目的でこれを含めました。

Gradle プロジェクトの場合

Gradle プロジェクトを操作するには:

  1. 次のように、共通リポジトリの依存関係のカスタム構成を追加します。

    ext {
        contractsGroupId = "com.example"
        contractsArtifactId = "common-repo"
        contractsVersion = "1.2.3"
    }
    
    configurations {
        contracts {
            transitive = false
        }
    }
  2. 次のように、共通リポジトリの依存関係をクラスパスに追加します。

    dependencies {
        contracts "${contractsGroupId}:${contractsArtifactId}:${contractsVersion}"
        testCompile "${contractsGroupId}:${contractsArtifactId}:${contractsVersion}"
    }
  3. 次のように、依存関係を適切なフォルダーにダウンロードします。

    task getContracts(type: Copy) {
        from configurations.contracts
        into new File(project.buildDir, "downloadedContracts")
    }
  4. 次のように JAR を解凍します。

    task unzipContracts(type: Copy) {
        def zipFile = new File(project.buildDir, "downloadedContracts/${contractsArtifactId}-${contractsVersion}.jar")
        def outputDir = file("${buildDir}/unpackedContracts")
    
        from zipTree(zipFile)
        into outputDir
    }
  5. 次のように、未使用の契約をクリーンアップします。

    task deleteUnwantedContracts(type: Delete) {
        delete fileTree(dir: "${buildDir}/unpackedContracts",
            include: "**/*",
            excludes: [
                "**/${project.name}/**"",
                "**/${first-topic}/**",
                "**/${second-topic}/**"])
    }
  6. 次のようにタスクの依存関係を作成します。

    unzipContracts.dependsOn("getContracts")
    deleteUnwantedContracts.dependsOn("unzipContracts")
    build.dependsOn("deleteUnwantedContracts")
  7. 次のように contractsDslDir プロパティを設定して、契約が含まれるディレクトリを指定してプラグインを構成します。

    contracts {
        contractsDslDir = new File("${buildDir}/unpackedContracts")
    }