Git運用手順(SI+Git未経験者多数の場合)

# 前提条件

プロジェクト全体の方針

- GitFlow戦略を採用

- チケット駆動で開発

- CIが提供されていない

- 開発工程の開始時点で、メンバー8人中5人が開発未経験、8人全員がGit未経験

  - 細かい粒度での標準化が急務

  - 経験者 + 自走できる未経験含むに対する未経験者の割合が多く、フォロー工数を捻出できない

 

# 採用した戦略と意図

- コミットメッセージにはconventional commitsを独自カスタムした仕様を策定

  - 記述パターンが少なく、キャッチアップが容易と判断

- VSCode拡張機能を採用し、原則コンソールを利用しない(拡張機能で操作できない場合は例外)

- コミット, プッシュ, PR作成やマージなどの基本操作に関する作業手順書を整備し、開発者が実施可能な操作をホワイトリスト形式で管理

- featureブランチ内のコミットメッセージはある程度作業者に任せる(管理しきれない)

  - 日々の進捗確認、早期フォローを目的に、日次で成果をプッシュしてもらうことだけルール化(後に安定期に入り、正しく形骸化した)

- featureからdevelopへのマージは常にスカッシュ

  - スカッシュすることで、featureブランチ内の粒度の揃わないコミットをdevelopに持ち込ませない

  - スカッシュマージ時のメッセージさえ合っていれば、developはクリーンになる(作業者に書いてもらい、レビュアが確認)

  - 開発者はスカッシュコミットせず、スカッシュするのはレビュア

    - develop以上がクリーンであればよいため、複雑な作業や判断はなるべくレビュアに寄せる

CSVの標準仕様はなかった

以前調べたことのメモ

きっかけ

次のポストを参照したこと

RFC4180を読む

ポストの中で言及されているようにRFC4180を参照すると、確かにそのように書いてある

カンマ区切り値形式(CSV)は、かなり長い間、さまざまなスプレッドシートプログラム間でデータを交換および変換するために使用されてきました。驚くべきことに、この形式は非常に一般的ですが、正式に文書化されたことはありません。

RFC 4180 - Common Format and MIME Type for Comma-Separated Values (CSV) Files

CSV形式にはさまざまな仕様と実装がありますが(例:[4]、[5]、[6]、[7])、CSVファイルのさまざまな解釈を可能にする正式な仕様は存在しません。このセクションでは、ほとんどの実装が従っていると思われる形式を文書化します。

RFC 4180 - Common Format and MIME Type for Comma-Separated Values (CSV) Files

所感

極端な例でいうと、メインフレーム由来のCSVでは1ファイル内で列数が異なるマルチフォーマット形式も出てくる

そうでなくとも他システムへCSV連携を行う場合は特に、相手とフォーマットの認識齟齬がないよう進めることを心がけたい

ヘッダあり、改行コードはCRLFで、と列挙して調整するよりも、サンプルを提示することで調整したほうが良さそう サンプルを提示する場合も、癖のあるパターンを盛り込んだほうがなおよい

癖のあるパターンの一例としては、値にカンマを含む場合のエスケープ処理方法など。

リードタイムアウトを実現できるモックサーバを用意するために、ncコマンドの挙動をパケットキャプチャで検証した

TL; DR

$ nc -l 8080 でサーバを立ち上げてリクエストを待ち受ける。

リクエストを受け取った後、そのままクライアントへレスポンスを返さなければ、クライアント側でリードタイムアウトを実現できる。コネクトタイムアウトにはならない

はじめに

所属プロジェクトの検証作業で、フロントエンドからバックエンド(BFF)へ通信するとき、リードタイムアウト(≠コネクトタイムアウト)した場合のフロントの挙動(エラーハンドリングの挙動)を確かめる必要があった

ローカル環境内でフロントエンド・バックエンドサーバ両方を立ち上げ、バックエンドのソース内にTime.sleep(100000)のような処理を埋めれば実現は簡単だが、 様々な制約条件によってローカル環境内でのバックエンドサーバの立ち上げが難しかったため、ローカル環境のバックエンドサーバの代替が必要だった

用語の定義

コネクトタイムアウト:接続が確立できていない(3way handshakeが完了しなかった)ので、クライアント側で通信を破棄する

リードタイムアウト:接続は確立できたが、応答に時間がかかりすぎるためにクライアント側で通信を破棄する

検証方針

WSL環境(Linux環境)はあったため、ncコマンドでサーバを立ち上げることでリードタイムアウト用のモックサーバを作成できるのではと考えた

(HTTPリクエストを受け取っても、サーバからはクライアントへ何も返信せずにいると、リードタイムアウトになるのではと考えた)

ここではコネクトタイムアウトではなく、あくまでリードタイムアウトを実現したいため、少なくともコネクトタイムアウトにならないことを証明する必要がある

これを証明するためには、パケットキャプチャを行い、3way handshakeが完了していることが読み取れるとよいはず

検証内容1(パケットキャプチャによる検証)

WSL環境内でセッションを3つ立ち上げる

  1. $ sudo tcpdump -i lo tcp port 12345 # 該当portでのパケットキャプチャ

  2. $ nc -l 12345 # TCPサーバのlisten

  3. $ nc localhost 12345 # TCPクライアントのlisten

0~2の操作の結果、tcpdumpのログ3行が出力され、それぞれFlags [S], Flags[S.], Flags[.]という表記が読み取れた

manpageの例によると、それぞれSYN, SYN+ACK, ACKと理解してよい

そのため3way handshakeは完了していると考えてよく、接続は確立できている(コネクトタイムアウトにはならない)

検証内容2(curl)

タイムアウトオプションつきでcurlを実行すると、3秒ではなく10秒ほどでリクエストがタイムアウトした

リードタイムアウトできていると考えてよさそう

$ curl --connect-timeout 3 --max-time 10 http://localhost:12345
curl: (28) Operation timed out after 10003 milliseconds with 0 bytes received

gitのマージ戦略

Excalidrawというサービス1を使って、Gitのブランチ戦略(マージ戦略)の図を書いてみた。

参画プロジェクトではGit Flowをベースとしたブランチ戦略を採用している。

さらに、featureブランチ内で生じた価値の低いコミット(typoの修正など)をdevelopに持ち込まないように、スカッシュマージも採用している。

この資料を用いて、どのような意図でブランチ戦略(マージ戦略)を採用・運用しているのかをメンバーに伝える予定。

GitHub Copilotを使った検証が便利

「ここでSystem.out.printlnしたいんでしょう?」とか、

「いい感じにクラス作っときますね」とか

GitHub Copilotが検証の準備をガンガン代わりにやってくれて、煩わしい作業が一気に排除できている

そしてコメントに指示や質問を書けば、それっぽい処理や回答を予測で表示してくれる

ただ、文法的に正しいことはクロスチェックすれば分かるけど、その手法がメジャーであることは証明しづらい

(GitHubの大量のソースコードをインプットにしているはずなので、突拍子もない処理は書いてないとは思いたい)

【WIP】JUnit(REST Assured)でQuarkus製アプリをデバッグ実行するとタイムアウトする問題を解決した

検証環境

  • JUnit 5.10.2
  • REST Assured 5.4.0
  • Java 17.0.5

事象

JUnit(REST Asssured)を使ったテストデバッグでステップ実行を行っていると、 java.net.SocketTimeoutException: Read timed outが発生した。

解決方法

@QuarkusTest
public class ApiTest {
    @Test
    public void testHelloEndpoint() {

        given()
                .config(RestAssured.config()
                        .httpClient(HttpClientConfig.httpClientConfig()
                                .setParam("http.socket.timeout", 1000000)))
                .when()
                .log().all()
                .get("hogehoge-service/hogehoge-service/sampleA/hello")
                .then()
                .log().all()
                .statusCode(200);
    }

}

参考

【Quarkus】OpenAPI Generator(jaxrs-spec)で生成したソースが、エンドポイントがうまく指定できずREST Assuredでテストできない

TL;DR

  • 生成対象となるOpenAPIでpathを1種類しか持たないとき、
    • クラス単位に設定される@Pathにフルパスが設定される
    • メソッド単位にパスは設定されない
  • Quarkusが提供するテスト用アノテーション@TestHTTPResourceは、クラス単位に設定された@Pathまでを注入する
  • したがって、クラス単位に設定したPathにパスパラメータが含まれている場合、@TestHTTPResourceを利用したテストができない
  • @TestHTTPResourceを外してフルパスでテストするしかなさそう

環境

動作環境

  • Ubuntu 20.04(WSL2)
  • openapitools/openapi-generator-cli(Dockerイメージ)

設定値

library: quarkus
additionalProperties:
  dateLibrary: java8-localdatetime
  hideGenerationTimestamp: true
  openApiNullable: false
  useBeanValidation: true
  useRuntimeException: true
  microprofileRestClientVersion: "3.0"
  serializationLibrary: "jackson"
  useJakartaEe: true
  generateBuilders: true
  interfaceOnly: true
  useSwaggerAnnotations: false
  sourceFolder: "src/main/java"

実行コマンドの例

GENERATOR=jaxrs-spec && docker run --rm -v ${PWD}:/local openapitools/openapi-generator-cli generate -i /local/input/petstore_2path.yaml -g ${GENERATOR} -o /local/out2/${GENERATOR} -c /local/input/config_server.yaml

試したこと(エンドポイントの個数によって生成のされ方が異なる)

エンドポイントが1種類のとき

openapi: "3.0.0"
info:
  version: 1.0.0
  title: Swagger Petstore
  license:
    name: MIT
servers:
  - url: http://petstore.swagger.io/v1
paths:
  /pets/{id}:
    post:
      summary: "Add a new pet to the store"
      description: "desc"
      operationId: "methodpost"
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: integer
          description: ユーザID
      responses:
        200:
          description: A paged array of pets
          headers:
            x-next:
              description: A link to the next page of responses
              schema:
                type: string
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/PetsResponse"
                type: object
                properties:
                  user:
                    type: object
                    properties:
                      age:
                        type: string
                      sex:
                        type: string
    put:
      summary: "Add a new pet to the store"
      description: "desc"
      operationId: "methodput"
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: integer
          description: ユーザID
      responses:
        200:
          description: A paged array of pets
          headers:
            x-next:
              description: A link to the next page of responses
              schema:
                type: string
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/PetsResponse"
                type: object
components:
  schemas:
    Petfoo:
      required:
        - id
        - name
      properties:
        id:
          type: integer
          format: int64
        name:
          type: string
        tag:
          type: string
    Petshoge:
      type: array
      items:
        $ref: "#/components/schemas/Petfoo"

    PetsResponse:
      properties:
        id:
          type: integer
          format: int64
          example: 3
        name:
          type: string
          example: "wan"
        tag:
          type: string
          example: "inu"
    Error:
      required:
        - code
        - message
      properties:
        code:
          type: integer
          format: int32
        message:
          type: string
package org.openapitools.api;

import org.openapitools.model.PetsResponse;

import jakarta.ws.rs.*;
import jakarta.ws.rs.core.Response;




import java.io.InputStream;
import java.util.Map;
import java.util.List;
import jakarta.validation.constraints.*;
import jakarta.validation.Valid;


@Path("/pets/{id}")
@jakarta.annotation.Generated(value = "org.openapitools.codegen.languages.JavaJAXRSSpecServerCodegen", comments = "Generator version: 7.5.0-SNAPSHOT")
public interface PetsApi {

    @POST
    @Produces({ "application/json" })
    PetsResponse methodpost(@PathParam("id") Integer id);

    @PUT
    @Produces({ "application/json" })
    PetsResponse methodput(@PathParam("id") Integer id);
}

エンドポイントが2種類のとき

openapi: "3.0.0"
info:
  version: 1.0.0
  title: Swagger Petstore
  license:
    name: MIT
servers:
  - url: http://petstore.swagger.io/v1
paths:
  /pets/{id}:
    post:
      summary: "Add a new pet to the store"
      description: "desc"
      operationId: "methodpost"
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: integer
          description: ユーザID
      responses:
        200:
          description: A paged array of pets
          headers:
            x-next:
              description: A link to the next page of responses
              schema:
                type: string
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/PetsResponse"
                type: object
                properties:
                  user:
                    type: object
                    properties:
                      age:
                        type: string
                      sex:
                        type: string
    put:
      summary: "Add a new pet to the store"
      description: "desc"
      operationId: "methodput"
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: integer
          description: ユーザID
      responses:
        200:
          description: A paged array of pets
          headers:
            x-next:
              description: A link to the next page of responses
              schema:
                type: string
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/PetsResponse"
                type: object
  /pets/name/{name}:
    post:
      summary: "Add a new pet to the store"
      description: "desc"
      operationId: "anothermethodpost"
      parameters:
        - name: name
          in: path
          required: true
          schema:
            type: integer
          description: 顧客名
      responses:
        200:
          description: A paged array of pets
          headers:
            x-next:
              description: A link to the next page of responses
              schema:
                type: string
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/PetsResponse"
                type: object
                properties:
                  user:
                    type: object
                    properties:
                      age:
                        type: string
                      sex:
                        type: string
components:
  schemas:
    Petfoo:
      required:
        - id
        - name
      properties:
        id:
          type: integer
          format: int64
        name:
          type: string
        tag:
          type: string
    Petshoge:
      type: array
      items:
        $ref: "#/components/schemas/Petfoo"

    PetsResponse:
      properties:
        id:
          type: integer
          format: int64
          example: 3
        name:
          type: string
          example: "wan"
        tag:
          type: string
          example: "inu"
    Error:
      required:
        - code
        - message
      properties:
        code:
          type: integer
          format: int32
        message:
          type: string
package org.openapitools.api;

import org.openapitools.model.PetsResponse;

import jakarta.ws.rs.*;
import jakarta.ws.rs.core.Response;




import java.io.InputStream;
import java.util.Map;
import java.util.List;
import jakarta.validation.constraints.*;
import jakarta.validation.Valid;


@Path("/pets")
@jakarta.annotation.Generated(value = "org.openapitools.codegen.languages.JavaJAXRSSpecServerCodegen", comments = "Generator version: 7.5.0-SNAPSHOT")
public interface PetsApi {

    @POST
    @Path("/name/{name}")
    @Produces({ "application/json" })
    PetsResponse anothermethodpost(@PathParam("name") Integer name);

    @POST
    @Path("/{id}")
    @Produces({ "application/json" })
    PetsResponse methodpost(@PathParam("id") Integer id);

    @PUT
    @Path("/{id}")
    @Produces({ "application/json" })
    PetsResponse methodput(@PathParam("id") Integer id);
}

参考