Git運用手順(SI+Git未経験者多数の場合)
# 前提条件
プロジェクト全体の方針
- GitFlow戦略を採用
- チケット駆動で開発
- CIが提供されていない
- 開発工程の開始時点で、メンバー8人中5人が開発未経験、8人全員がGit未経験
- 細かい粒度での標準化が急務
- 経験者 + 自走できる未経験含むに対する未経験者の割合が多く、フォロー工数を捻出できない
# 採用した戦略と意図
- コミットメッセージにはconventional commitsを独自カスタムした仕様を策定
- 記述パターンが少なく、キャッチアップが容易と判断
- VSCode拡張機能を採用し、原則コンソールを利用しない(拡張機能で操作できない場合は例外)
- コミット, プッシュ, PR作成やマージなどの基本操作に関する作業手順書を整備し、開発者が実施可能な操作をホワイトリスト形式で管理
- featureブランチ内のコミットメッセージはある程度作業者に任せる(管理しきれない)
- 日々の進捗確認、早期フォローを目的に、日次で成果をプッシュしてもらうことだけルール化(後に安定期に入り、正しく形骸化した)
- featureからdevelopへのマージは常にスカッシュ
- スカッシュすることで、featureブランチ内の粒度の揃わないコミットをdevelopに持ち込ませない
- スカッシュマージ時のメッセージさえ合っていれば、developはクリーンになる(作業者に書いてもらい、レビュアが確認)
- 開発者はスカッシュコミットせず、スカッシュするのはレビュア
- develop以上がクリーンであればよいため、複雑な作業や判断はなるべくレビュアに寄せる
CSVの標準仕様はなかった
以前調べたことのメモ
きっかけ
次のポストを参照したこと
「一般的なCSV」というものは無いという風に理解してください。相手がCSVと言った時には特に何も説明していないとみなして、詳細を確認する事をおすすめします。あらゆる場所に独自形式が存在するのがCSVです。 https://t.co/nPVOm3wAcx
— 太一 (@ryushi) 2024年8月29日
いうてもRFCやぞ……?と思いながら読みに行ったら「びっくりすると思うけど、CSVという形式の仕様は正式に文書化されたことがない」「仕様も実装もめっちゃあって正式な仕様とかないんだけど、最大公約になりそうなルールだけ挙げとくね」みたいな話が書いてあってぐだぐだだったw https://t.co/6Bh4zs2mnv
— なかざん (@Nkzn) 2024年8月29日
RFC4180を一通り読んだんだけど、
— なかざん (@Nkzn) 2024年8月29日
・MIME typeへの登録にはRFC2048で定められた文書化が必要であるが、CSVには該当する文書がなかった
・こんだけ使われてるCSVにMIME typeがないのもアレなので、RFC4180でCSV仕様のぐだぐだな状況を紹介して文書だと言い張ることにする
ってことだよねえこれ? https://t.co/R0qYGhhRZI
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つ立ち上げる
$ sudo tcpdump -i lo tcp port 12345 # 該当portでのパケットキャプチャ$ nc -l 12345 # TCPサーバのlisten$ 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
【WIP】JUnit(REST Assured)でQuarkus製アプリをデバッグ実行するとタイムアウトする問題を解決した
検証環境
事象
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を外してフルパスでテストするしかなさそう
環境
動作環境
設定値
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); }

