diff --git a/typing-server/Dockerfile b/typing-server/Dockerfile new file mode 100644 index 0000000..ea4df5a --- /dev/null +++ b/typing-server/Dockerfile @@ -0,0 +1,29 @@ +# 基本イメージ +FROM golang:1.22.0 as builder + +# 作業ディレクトリを設定 +WORKDIR /app + +# ソースコードをコピー +COPY . . + +# 依存関係をインストール +RUN go mod download + +# アプリケーションをビルド +RUN CGO_ENABLED=0 GOOS=linux go build -v -o server ./api/cmd/main.go + +# 実行イメージ +FROM alpine:latest +RUN apk --no-cache add ca-certificates + +# tzdataパッケージのインストール +RUN apk --no-cache add tzdata + +WORKDIR /root + +# ビルドしたバイナリをコピー +COPY --from=builder /app/server . + +# アプリケーションの実行 +CMD ["./server"] diff --git a/typing-server/api/cmd/main.go b/typing-server/api/cmd/main.go new file mode 100644 index 0000000..82272b5 --- /dev/null +++ b/typing-server/api/cmd/main.go @@ -0,0 +1,97 @@ +package main + +import ( + "context" + "log/slog" + "net/http" + "os" + "os/signal" + "sync" + "syscall" + "time" + + "github.com/go-sql-driver/mysql" + "github.com/su-its/typing/typing-server/api/presenter" + "github.com/su-its/typing/typing-server/domain/repository/ent" +) + +func main() { + logger := slog.Default() + + // タイムゾーンの設定 + jst, err := time.LoadLocation("Asia/Tokyo") + if err != nil { + logger.Error("failed to load location: %v", err) + return + } + + // MySQLの接続設定 + mysqlConfig := &mysql.Config{ + DBName: "typing-db", // データベース名 + User: "user", // ユーザー名 + Passwd: "password", // パスワード + Net: "tcp", // ネットワークタイプ + Addr: "db:3306", // アドレス(Docker Compose内でのサービス名とポート) + ParseTime: true, // 時刻をtime.Timeで解析する + Loc: jst, // タイムゾーン + } + + // entクライアントの初期化 + entClient, err := ent.Open("mysql", mysqlConfig.FormatDSN()) + if err != nil { + logger.Error("failed to open ent client: %v", err) + return + } + defer entClient.Close() + logger.Info("ent client is opened") + + // スキーマの作成 + if err := entClient.Schema.Create(context.Background()); err != nil { + logger.Error("failed to create schema: %v", err) + return + } + logger.Info("schema is created") + + // ルートの登録 + presenter.RegisterRoutes() + + // WaitGroupの宣言 + var wg sync.WaitGroup + // エラーを通知するためのチャネル + errChan := make(chan error, 1) + // シグナルハンドリングの準備 + sigChan := make(chan os.Signal, 1) + signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM) + // HTTPサーバーの非同期起動 + wg.Add(1) + go func() { + defer wg.Done() // 関数終了時にWaitGroupをデクリメント + // サーバーの設定 + server := &http.Server{Addr: ":8080"} + // 非同期でサーバーを開始 + go func() { + logger.Info("server is running at Addr :8080") + if err := server.ListenAndServe(); err != http.ErrServerClosed { + logger.Error("failed to listen and serve: %v", err) + errChan <- err // エラーをチャネルに送信 + } + }() + // シグナルを待機 + <-sigChan + logger.Info("shutting down the server...") + ctx := context.TODO() // Use context.TODO() as a temporary placeholder + if err := server.Shutdown(ctx); err != nil { + logger.Error("error during server shutdown: %v", err) + errChan <- err // エラーをチャネルに送信 + } + }() + select { + case <-errChan: // エラーが発生した場合 + logger.Error("server stopped due to an error") + case sig := <-sigChan: // シグナルを受信した場合 + logger.Info("received signal: %s", sig) + } + wg.Wait() // HTTPサーバーの終了を待機 + close(errChan) + logger.Info("server exited") +} diff --git a/typing-server/api/controller/system/health.go b/typing-server/api/controller/system/health.go new file mode 100644 index 0000000..937ac73 --- /dev/null +++ b/typing-server/api/controller/system/health.go @@ -0,0 +1,20 @@ +package system + +import ( + "log/slog" + "net/http" +) + +// HealthCheck はヘルスチェックのためのハンドラー関数です。 +func HealthCheck(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusOK) + _, err := w.Write([]byte("API is running")) + if err != nil { + // エラーログを記録し、処理を終了します。 + // 実際には、この時点でレスポンスヘッダーやボディがクライアントに送信されている可能性が高いため、 + // http.Errorを呼び出すことは推奨されません。 + // 代わりに、ログに記録するなどのサーバー側での対応が適切です。 + slog.Error("failed to write response: %v", err) + } +} + diff --git a/typing-server/api/presenter/server.go b/typing-server/api/presenter/server.go new file mode 100644 index 0000000..e921a4e --- /dev/null +++ b/typing-server/api/presenter/server.go @@ -0,0 +1,11 @@ +package presenter + +import ( + "net/http" + + "github.com/su-its/typing/typing-server/api/controller/system" +) + +func RegisterRoutes() { + http.HandleFunc("/health", system.HealthCheck) +} diff --git a/typing-server/docker-compose.dev.yml b/typing-server/docker-compose.dev.yml new file mode 100644 index 0000000..629343f --- /dev/null +++ b/typing-server/docker-compose.dev.yml @@ -0,0 +1,46 @@ +services: + api: + depends_on: + db: + condition: service_healthy + build: + context: . + dockerfile: Dockerfile + volumes: + - ./api:/app + ports: + - "8080:8080" + networks: + app_net: + ipv4_address: '172.28.1.3' + extra_hosts: + - 'db:172.28.1.5' + db: + healthcheck: + test: ["CMD", "mysqladmin", "ping", "-h", "localhost"] + interval: 10s + retries: 10 + start_period: 30s + + image: mysql:8.3.0 + environment: + MYSQL_DATABASE: typing-db + MYSQL_USER: user + MYSQL_PASSWORD: password + MYSQL_ROOT_PASSWORD: password + ports: + - "3307:3306" + volumes: + - db-data:/var/lib/mysql + networks: + app_net: + ipv4_address: '172.28.1.5' +volumes: + db-data: +networks: + app_net: + driver: bridge + ipam: + driver: default + config: + - subnet: 172.28.1.0/24 diff --git a/typing-server/docker-compose.yml b/typing-server/docker-compose.yml new file mode 100644 index 0000000..629343f --- /dev/null +++ b/typing-server/docker-compose.yml @@ -0,0 +1,46 @@ +services: + api: + depends_on: + db: + condition: service_healthy + build: + context: . + dockerfile: Dockerfile + volumes: + - ./api:/app + ports: + - "8080:8080" + networks: + app_net: + ipv4_address: '172.28.1.3' + extra_hosts: + - 'db:172.28.1.5' + db: + healthcheck: + test: ["CMD", "mysqladmin", "ping", "-h", "localhost"] + interval: 10s + retries: 10 + start_period: 30s + + image: mysql:8.3.0 + environment: + MYSQL_DATABASE: typing-db + MYSQL_USER: user + MYSQL_PASSWORD: password + MYSQL_ROOT_PASSWORD: password + ports: + - "3307:3306" + volumes: + - db-data:/var/lib/mysql + networks: + app_net: + ipv4_address: '172.28.1.5' +volumes: + db-data: +networks: + app_net: + driver: bridge + ipam: + driver: default + config: + - subnet: 172.28.1.0/24 diff --git a/typing-server/go.mod b/typing-server/go.mod index 5130d9a..4d5c904 100644 --- a/typing-server/go.mod +++ b/typing-server/go.mod @@ -4,6 +4,7 @@ go 1.22.0 require ( entgo.io/ent v0.13.1 + github.com/go-sql-driver/mysql v1.7.0 github.com/google/uuid v1.6.0 ) diff --git a/typing-server/go.sum b/typing-server/go.sum index 89035ab..a1a1a0e 100644 --- a/typing-server/go.sum +++ b/typing-server/go.sum @@ -12,6 +12,8 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/go-openapi/inflect v0.19.0 h1:9jCH9scKIbHeV9m12SmPilScz6krDxKRasNNSNPXu/4= github.com/go-openapi/inflect v0.19.0/go.mod h1:lHpZVlpIQqLyKwJ4N+YSc9hchQy/i12fJykb83CRBH4= +github.com/go-sql-driver/mysql v1.7.0 h1:ueSltNNllEqE3qcWBTD0iQd3IpL/6U+mJxLkazJ7YPc= +github.com/go-sql-driver/mysql v1.7.0/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI= github.com/go-test/deep v1.0.3 h1:ZrJSEWsXzPOxaZnFteGEfooLba+ju3FYIbOrS+rQd68= github.com/go-test/deep v1.0.3/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=