Go e l'ecosistema OpenAPI
μλ‘
Go μΈμ΄λ‘ Production Backend μλ²λ₯Ό κ°λ°νλ€ λ³΄λ©΄ κ±°μ λλΆλΆμ κ°λ°μλ€μ΄ κ°μ₯ μ²μμΌλ‘ λ§λλ λμ μ€ νλλ λ€μκ³Ό κ°μ΅λλ€.
API λ¬Έμν, μ΄λ»κ² νμ§...?
μ΄μ λνμ¬ μ‘°κΈλ§ μ°Ύμ보면 OpenAPI μ€νμ λ§λ λ¬Έμλ₯Ό μμ±νλ κ²μ΄ μ΄λ‘λ€λ μ¬μ€μ κΉ¨λ«κ² λκ³ , μμ°μ€λ½κ² OpenAPIμ μ°λλλ λΌμ΄λΈλ¬λ¦¬λ₯Ό μ°Ύκ² λ©λλ€. νμ§λ§ μ΄λ¬ν κ²°μ μ μΈμλ κ·Έ λ€μ λ¬Έμ κ° μ‘΄μ¬ν©λλ€.
OpenAPI κ΄λ ¨ λΌμ΄λΈλ¬λ¦¬ λ§μλ°.. λ μ¨μΌνμ§...?
μ΄ λ¬Έμλ μ΄λ¬ν μν©μ κ²½ννκ³ κ³μ Go μ λ¬Έμλ€μ μνμ¬ μμ±ν κ°λ΅ν λΌμ΄λΈλ¬λ¦¬ μκ°κΈ μ λλ€. 2024λ λ§ κΈ°μ€μΌλ‘ μμ±λ λ¬Έμμ΄λ©°, μΈμ΄ μνκ³λ νμ μ λμ μΌλ‘ λ°λλ λ§νΌ μ°Έκ³ νλ©΄μ νμ μ΅μ κ·Όν©λ μ΄ν΄λ³΄λκ²μ μΆμ²λ립λλ€.
OpenAPI λ₯Ό λνλ λΌμ΄λΈλ¬λ¦¬λ€μ μ λ΅
μ΄λ―Έ μκ³ κ³μ λΆλΆμ΄κ² μ§λ§, OpenAPI λ REST APIλ₯Ό λͺ ννκ² μ μνκ³ λ¬ΈμννκΈ° μν μ€νμ λλ€. APIμ μλν¬μΈνΈ, μμ², μλ΅ νμ λ±μ YAML λλ JSON νμμΌλ‘ μ μνμ¬ κ°λ°μλ€ λΏλ§ μλλΌ νλ‘ νΈλ¨, λ°±μλλ¨ μ½λ μμ±μ μλννμ¬ λ¬΄μλ―Έν λ°λ³΅μ μ€μ¬μ£Όκ³ μμν ν΄λ¨Όμλ¬λ€μ μ€μ¬μ£Όλλ° ν° λμμ μ€λλ€.
μ΄λ¬ν OpenAPI λ₯Ό νλ‘μ νΈμ μμ°μ€λ½κ² κ²°ν©μν€κΈ° μν΄ Go μνκ³μ λΌμ΄λΈλ¬λ¦¬λ€μ ν¬κ² λ€μ μΈκ°μ§ μ λ΅μ μ·¨ν©λλ€.
1. Go μ£Όμμ OpenAPI μ€ν λ¬Έμλ‘ μ‘°ν©
OpenAPI μ λ§μΆ°μ API λ₯Ό κ°λ°ν λ κΉλ€λ‘μ΄μ μ€ νλλ μ€μ λ¬Έμμ ν΄λΉ λ¬Έμλ₯Ό ꡬνν μ½λκ° λ³λμ νμΌλ‘ μ ν λ€λ₯ΈμμΉμ μ‘΄μ¬νλ€λ³΄λ, μ½λλ₯Ό μ λ°μ΄νΈ νλλ° λ¬Έμλ₯Ό μ λ°μ΄νΈ μνλκ° λ¬Έμλ μ λ°μ΄νΈ νλλ° μ½λλ₯Ό μ λ°μ΄νΈ νμ§ λͺ»νλ μν©μ΄ μκ°λ³΄λ€ μ¦λ€λ κ²μ λλ€.
κ°λ¨ν μμλ₯Ό λ€μ΄λ³΄λ©΄
./internal/server/user.goλΌλ νμΌ μμμ API μ λν λ‘μ§μ μμ νλλ°- μ€μ λ¬Έμλ
./openapi3.yamlμ μ‘΄μ¬νκ³ , μ΄μλν λ³κ²½μ μ€μλ‘ κΉλΉ‘ν μ μμ΅λλ€. - μ΄λ¬ν λ³κ²½μ¬νμ λν μ΄μλ₯Ό μΈμ§νμ§ λͺ»νκ³ Pull Request λ₯Ό λ λ¦¬κ³ λλ£λ€μκ² λ¦¬λ·°λ₯Ό λ°μ κ²½μ°
- 리뷰μ΄λ€ λν
./openapi3.yamlμ λν λ³κ²½μ¬νμ΄ λμ 보μ΄μ§ μκΈ° λλ¬Έμ API μ€νμ κ·Έλλ‘μΈλ° μ€μ API ꡬν체λ λ³κ²½μ΄ λμ΄λ²λ¦¬λ λΆμμ¬κ° λ°μν μ μμ΅λλ€.
Go μ£Όμμ ννλ‘ API λ¬Έμλ₯Ό μμ±νλ©΄ μ΄λ¬ν λ¬Έμ λ₯Ό μ΄λ μ λ ν΄μν μ μμ΅λλ€. μ½λμ λ¬Έμκ° ν κ³³μ λͺ¨μ¬ μκΈ° λλ¬Έμ, μ½λλ₯Ό μμ νλ©΄μ μ£Όμλ ν¨κ» μ λ°μ΄νΈν μ μμ΅λλ€. μ΄λ¬ν μ£Όμμ κΈ°λ°μΌλ‘ μλμΌλ‘ OpenAPI μ€ν λ¬Έμλ₯Ό μμ±ν΄μ£Όλ λꡬλ€μ΄ μ‘΄μ¬ν©λλ€.
λνμ μΈ νλ‘μ νΈλ‘λ Swagκ° μμ΅λλ€. Swagλ Go μ½λμ μ£Όμμ νμ±νμ¬ OpenAPI 2 νμμ λ¬Έμλ₯Ό μμ±ν΄ μ€λλ€. μ¬μ© λ°©λ²μ κ°λ¨ν©λλ€. νΈλ€λ¬ ν¨μ μμ κ° λΌμ΄λΈλ¬λ¦¬μμ μ ν νμμ λ§κ² μ£Όμμ μμ±νλ©΄ λ©λλ€.
1// @Summary μ μ μμ±
2// @Description μλ‘μ΄ μ μ λ₯Ό μμ±ν©λλ€.
3// @Tags Users
4// @Accept json
5// @Produce json
6// @Param user body models.User true "μ μ μ 보"
7// @Success 200 {object} models.User
8// @Failure 400 {object} models.ErrorResponse
9// @Router /users [post]
10func CreateUser(c *gin.Context) {
11 // ...
12}
μ΄λ κ² μ£Όμμ μμ±νλ©΄ Swag λΌλ CLI λ μ΄ μ£Όμλ€μ νμ±ν΄μ OpenAPI 2 λ¬Έμλ₯Ό μμ±ν©λλ€. μΌλ°μ μΌλ‘ CI κ³Όμ μ μ΄λ¬ν μμ μ΄ νν΄μ§λ©°, μμ±λ OpenAPI μ€νμ λ¬Έμλ Git Repository, μ΅μ’ λΉλ κ²°κ³Όλ¬Ό, λ³λμ μΈλΆ API λ¬Έμ κ΄λ¦¬ μμ€ν μ λ°°ν¬λμ΄ λ€λ₯Έ νλ‘μ νΈμμ νμ λ μ¬μ©λκ² λ©λλ€.
μ₯μ :
- μ£Όμμ΄ μ½λμ ν¨κ» μκΈ° λλ¬Έμ μ€μ μ½λμ λ¬Έμμ νμμ΄ λ¬λΌμ§ κ°λ₯μ±μ΄ μ€μ΄λλλ€.
- λ³λμ λꡬλ 볡μ‘ν μ€μ μμ΄ μ£Όμλ§μΌλ‘ κ°νΈνκ³ μμ λ‘κ² λ¬Έμνλ₯Ό ν μ μμ΅λλ€.
- μ£Όμμ΄ μ€μ API λ‘μ§μ μν₯μ μ£Όμ§ μκΈ°λλ¬Έμ, λ¬Έμλ‘ κ³΅κ°νκΈ° λΆλ΄μ€λ¬μ΄ μμ κΈ°λ₯μ μΆκ°νκΈ° μ’μ΅λλ€.
λ¨μ :
- μ£Όμμ λΌμΈμκ° λ§μμ§λ©΄μ λ¨μΌ μ½λ νμΌμ λν κ°λ μ±μ΄ λ¨μ΄μ§ μ μμ΅λλ€.
- μ£Όμμ ννλ‘ λͺ¨λ API μ€νμ νννκΈ° μ΄λ €μΈ μ μμ΅λλ€.
- λ¬Έμκ° μ½λλ₯Ό κ°μ νλκ²μ μλκΈ°λλ¬Έμ OpenAPI λ¬Έμμ μ€μ λ‘μ§μ΄ μΌμΉνλ€λ 보μ₯μ ν μ μμ΅λλ€.
2. OpenAPI μ€νμ λ¬Έμλ‘ Go μ½λλ₯Ό μμ±
Single source of Truth (SSOT) λ₯Ό Go μ½λκ° μλλΌ λ¬Έμμͺ½μ λλ λ°©λ²λ μ‘΄μ¬ν©λλ€. λ°λ‘ OpenAPI μ€νμ λ¨Όμ μ μνκ³ , μ μλ λ΄μ©μ κΈ°λ°μΌλ‘ Go μ½λλ₯Ό μμ±νλ λ°©μμ λλ€. API μ€νμ΄ κ³§ μ½λλ₯Ό μμ±ν΄μ£ΌκΈ° λλ¬Έμ κ°λ° λ¬Ένμ μΌλ‘ API μ€κ³λ₯Ό λ¨Όμ νλκ²μ κ°μ ν μ μμΌλ©° κ°λ° μμμ μΌλ‘ API μ€νμ μ μνλκ²μ΄ κ°μ₯λ¨Όμ μμμ΄ λκΈ°λλ¬Έμ κ°λ°μ΄ μλ£λκ³ λμμΌ λμΉλΆλΆμ μΈμ§νκ³ API μ€ν λ³κ²½κ³Ό ν¨κ» μ 체 μ½λκ° μμ λλ λΆμμ¬λ₯Ό μ‘°κΈ°μ λ°©μ§ν μ μλ κ°μ μ κ°μ§κ³ μμ΅λλ€.
μ΄ λ°©μμ μ±ννλ λνμ μΈ νλ‘μ νΈλ‘λ oapi-codegen κ³Ό OpenAPI Generator κ° μ‘΄μ¬ν©λλ€. μ¬μ©λ²μ κ°λ¨ν©λλ€.
- OpenAPI μ€νμ λ§κ² yaml νΉμ json λ¬Έμλ₯Ό μμ±νκ³
- CLI λ₯Ό μ€ννλ©΄
- κ·Έμ λμλλ Go stub μ½λκ° μμ±λ©λλ€.
- μ΄μ μ΄ stub μ΄ μ¬μ©ν μ μλλ‘ κ°λ³ API μ λν μΈλΆ λ‘μ§λ§ μ§μ ꡬννλ©΄ λ©λλ€.
λ€μμ oapi-codegen μμ μμ±ν΄μ£Όλ μ½λμ μμμ λλ€.
1// StrictServerInterface represents all server handlers.
2type StrictServerInterface interface {
3 // ...
4 // Returns all pets
5 // (GET /pets)
6 FindPets(ctx context.Context, request FindPetsRequestObject) (FindPetsResponseObject, error)
7 // ...
8}
μ interface λ₯Ό λ§€κ°λ‘ oapi-codegen μ΄ μμ±ν΄μ€ μ½λλ query parameters, header, body νμ± λ° Validation λ±μ λ‘μ§μ μννκ³ interface μ μ μΈλ μ μ ν method λ₯Ό νΈμΆν΄μ£Όλ ꡬ쑰μ λλ€. μ¬μ©μλ μ interface μ λν ꡬνμ²΄λ§ κ΅¬ννλ©΄ API ꡬνμ νμν μμ μ΄ μλ£λκ² λ©λλ€.
μ₯μ :
- μ€νμ΄ λ¨Όμ λμ€κ³ κ°λ°μ΄ μ§νλκΈ° λλ¬Έμ μ¬λ¬ νμμ νμ νλκ²½μ° μ 무λ₯Ό λ³λ ¬μ μΌλ‘ μ§ννκΈ° μ 리ν©λλ€.
- λ°λ³΅μ± λ Έκ°λ€λ‘ μμ νλ λΆλΆμ λν μ½λκ° μλμΌλ‘ μμ±λκΈ° λλ¬Έμ, μ 무 ν¨μ¨μ΄ μμΉνλ©΄μλ λλ²κΉ μ μ¬μ ν μ 리ν©λλ€.
- λ¬Έμμ μ½λμ νμμ΄ νμ μΌμΉνλ€λκ²μ 보μ₯νκΈ° μ½μ΅λλ€.
λ¨μ :
- OpenAPI μ€ν μ체μ 무μ§ν μνμΌκ²½μ° μ΄κΈ° λ¬λ컀λΈκ° λ€μ μ‘΄μ¬ν©λλ€.
- API λ₯Ό νΈλ€λ§νλ μ½λμ νμμ΄ νλ‘μ νΈμ μν΄μ μλμΌλ‘ μμ±λκΈ° λλ¬Έμ 컀μ€ν°λ§μ΄μ§μ΄ νμνκ²½μ° λμνκΈ° μ΄λ €μΈ μ μμ΅λλ€.
μ μμ μ½λ©νΈ. 2024λ 10μ κΈ°μ€ OpenAPI Generator κ° μμ±ν Go μ½λλ API λ‘μ§λΏλ§ μλλΌ μ 체 νλ‘μ νΈ νμμ κ°μ νλ©° νλ‘μ νΈμ κ΅¬μ‘°κ° κ²½μ§λμ΄μμ΄ μ€μ Production νκ²½μ νμν λ€μν κΈ°λ₯λ€μ μΆκ°νκΈ°μλ λΆμ ν©ν ννμ μ½λλ₯Ό μμ±νκ³ μμ΅λλ€. μ΄ λ°©μμ μ±ννμλ λΆλ€μ oapi-codegen μ μ¬μ©νμλκ²μ μ κ·Ήμ μΌλ‘ κΆμ₯λ립λλ€. μ μλ, oapi-codege + echo + StrictServerInterface λ₯Ό μ¬μ©νκ³ μμ΅λλ€.
3. Go μ½λλ‘ OpenAPI μ€ν λ¬Έμλ₯Ό μμ±
μμ, μλ°±λͺ μ μ¬λλ€μ΄ κ°μ μλ²μλν΄μ κ°λ°μ μ§ννλ€λ³΄λ©΄ νμ°μ μΌλ‘ λ°μνλ μ΄μκ° κ°λ³ API λ³λ‘ ν΅μΌμ±μ΄ κΉ¨μ§ μ μλ€λ κ²μ λλ€. μ§κ΄μ μΈ μμλ‘ 100κ°κ° λμ΄κ°λ API Endpoint μ λν λͺ μΈλ₯Ό νλμ OpenAPI yaml νμΌμ μ μΈν κ²½μ° ν΄λΉ νμΌμ 1λ§ λΌμΈμ΄ λμ΄κ°λ κ΄΄λ¬Όμ΄ λμ΄μμ κ²μ΄κ³ μλ‘μ΄ API Endpoint λ₯Ό μ μΈνλ©΄μ νμ°μ μΌλ‘ κ°μ λͺ¨λΈμ μ€λ³΅ν΄μ μ μΈνλ€λκ° λͺλͺ νλλ₯Ό λλ½νλ€λκ°, 컨벀μ μ λ§μ§ μλ Path λ€μ΄λ°μ΄ νμνλ€λκ°μ κ°μ μ 체μ μΈ API μ ν΅μΌμ±μ΄ κΉ¨μ§κΈ° μμνκ² λ©λλ€.
μ΄λ¬ν μ΄μλ₯Ό ν΄κ²°νκΈ°μν΄ OpenAPI yaml μ κ΄λ¦¬νλ Owner λ₯Ό λ°λ‘ λλ€λκ°, Linter λ₯Ό κ°λ°ν΄μ CI κ³Όμ μ€μ μλμΌλ‘ μ‘μλΌ μ μλλ‘ μ‘°μΉλ₯Ό μ·¨ν μλ μκ² μ§λ§ Go μΈμ΄λ‘ Domain-specific language (DSL) λ₯Ό μ μνμ¬ λͺ¨λ API κ° μΌκ΄μ μΈ ν΅μΌμ±μ κ°μ§ μ μλλ‘ κ°μ ν μ μμ΅λλ€.
μ΄λ¬ν κΈ°λ²μ μ¬μ©νλ λνμ μΈ νλ‘μ νΈκ° Kubernetes μ΄λ©° (λ³λ λΌμ΄λΈλ¬λ¦¬ μμ΄ μ체μ μΌλ‘ ꡬμΆ), go-restful, goa λ±μ νλ‘μ νΈλ₯Ό μ¬μ©ν΄μ μ¬μ©ν΄λ³Όμλ μμ΅λλ€. λ€μμ goa μ μ¬μ© μμμ
λλ€.
1var _ = Service("user", func() {
2 Method("create", func() {
3 Payload(UserPayload)
4 Result(User)
5 HTTP(func() {
6 POST("/users")
7 Response(StatusOK)
8 })
9 })
10})
μμκ°μ΄ μ»΄νμΌ κ°λ₯ν Go μ½λλ₯Ό μμ±νλ©΄ POST /users API μ λν ꡬνκ³Ό λ¬Έμμ λν μ μκ° λμμ μλ£λλ κ°μ μ μ»μ μ μμ΅λλ€.
μ₯μ :
- μ½λλ‘λΆν° λͺ¨λ κ² λμ€κΈ°λλ¬Έμ νλ‘μ νΈ μ 체μ λν API μΌκ΄μ±μ κ°μ§κ³ κ°κΈ°κ° μ½μ΅λλ€.
- Go μ κ°νμ μμ€ν μ νμ©νμ¬, OpenAPI3 μ λͺ¨λ κΈ°λ₯μ νμ©νμλλ³΄λ€ λ μ ννκ³ λ Όλμ΄ μλ μ€νμ μ»μ μ μμ΅λλ€.
λ¨μ :
- κ° νλ μμν¬μμ μ μν DSL μ μ΅νμΌνλ©°, κΈ°μ‘΄ μ½λμ μ μ©νκΈ°λ μ΄λ €μΈ μ μμ΅λλ€.
- νλ μμν¬μμ μ μν κ·μΉμ κ°μ λ‘ λ°λΌμΌ νλ―λ‘ μμ λ λ° μ μ°μ±μ΄ λ¨μ΄μ§ μ μμ΅λλ€.
λ§λ¬΄λ¦¬νλ©°
κ° λ°©λ²μ μ₯λ¨μ μ΄ μμΌλ©°, νλ‘μ νΈμ μꡬμ¬νκ³Ό νμ μ νΈλμ λ°λΌ μ ν©ν λ°©λ²μ μ ννλ κ²μ΄ μ€μν©λλ€. μΈμ λ μ μΌ μ€μνκ²μ μ΄λ€ λ°©μμ μ¬μ©νλκ² μ’μΌλκ° μλλΌ, νμ¬ μμ μ΄ μ²ν μν©μ κ°μ₯ μ ν©ν μ루μ μ 무μμΈμ§ κ°μΉνλ¨μ μννκ³ κ°λ° μμ°μ±μ λκ² κ°μ Έκ° λΉ λ₯Έ ν΄κ·Όκ³Ό ν‘μ‘±μ€λ¬μ΄ μλΌλ²¨μ μ¦κΈ°λ κ²μ λλ€.
νμ¬ 2024λ 10μμ κΈ°μ€μΌλ‘ κΈμ μμ±νκΈ΄ νμ§λ§ Goμ OpenAPI μνκ³λ μ§μμ μΌλ‘ λ°μ νκ³ μμΌλ―λ‘, μ΄ κΈμ μ½λ μμ κ°μ κ°κ²©μ κ³ λ €νμ¬ κ° λΌμ΄λΈλ¬λ¦¬λ€ λ° νλ‘μ νΈλ€μ κ·Όν©κ³Ό κ·Έλ€μ λ³κ²½λ μ₯λ¨μ λ μ§μμ μΌλ‘ νλ‘μ νμκΈΈ λ°λλλ€.
ν볡ν Go λΌμ΄ν λμΈμ~ π