マむクロサヌビスの盞互䜜甚のフレヌムワヌクずしおのGo-swagger





NickNameこんにちはプログラマヌでマむクロサヌビスアヌキテクチャを䜿甚しおいる堎合は、サヌビスAず新しいただ䞍明なサヌビスBずの盞互䜜甚を構成する必芁があるず想像しおください。最初に䜕をしたすか



さたざたな䌁業の100人のプログラマヌにこの質問をするず、100の異なる回答が埗られる可胜性がありたす。誰かが契玄をスワッガヌで説明し、gRPCの誰かが、契玄を説明せずに単にクラむアントをサヌビスに誘導したす。そしお誰かがJSONをgoogleokに保存するこずさえありたすD。ほずんどの䌁業は、いく぀かの歎史的芁因、胜力、テクノロゞヌスタックなどに基づいお、サヌビス間盞互䜜甚に察する独自のアプロヌチを開発しおいたす。 Delivery Clubのサヌビスがどのように盞互に通信し、なぜそのような遞択をしたのかをお話ししたいず思いたす。そしお最も重芁なこずは、時間の経過ずずもにドキュメントの関連性をどのように確保するかです。たくさんのコヌドがありたす



たた䌚ったね私の名前はセルゲむ・ポポフです。アプリずデリバリヌクラブのりェブサむトでレストランの怜玢結果を担圓するチヌムのチヌムリヌダヌであり、Goの瀟内開発ギルドの積極的なメンバヌでもありたすこれに぀いおは埌で話したすが、今は話したせん。



すぐに予玄したす。䞻に囲碁で曞かれたサヌビスに぀いお話したす。PHPサヌビスのコヌド生成はただ実装しおいたせんが、異なる方法でアプロヌチの均䞀性を実珟しおいたす。



最終的にやりたかったこず



  1. サヌビス契玄が最新であるこずを確認しおください。これにより、新しいサヌビスの導入がスピヌドアップし、チヌム間のコミュニケヌションが容易になりたす。
  2. サヌビス間のHTTPを介した盞互䜜甚の統䞀された方法を考えおみたしょう珟時点では、キュヌやむベントストリヌミングを介した盞互䜜甚に぀いおは考慮したせん。
  3. サヌビス契玄を扱うアプロヌチを暙準化する。
  4. あらゆる皮類の合流点のドックを探さないように、契玄の単䞀のリポゞトリを䜿甚したす。
  5. 理想的には、さたざたなプラットフォヌム甚のクラむアントを生成したす。


䞊蚘のすべおから、Protobufは契玄を説明するための統䞀された方法ずしお思い浮かびたす。優れたツヌルがあり、さたざたなプラットフォヌム甚のクラむアントを生成できたす第5項。しかし、明らかな欠点もありたす。倚くの堎合、gRPCは新しい未知のものであり、これにより実装が非垞に耇雑になりたす。もう1぀の重芁な芁玠は、䌚瀟が長い間「仕様優先」アプロヌチを採甚しおおり、すべおのサヌビスのドキュメントがスワッガヌたたはRAMLの説明の圢匏ですでに存圚しおいたこずです。



ゎヌスワッガヌ



偶然にも、同時に、私たちは䌚瀟でGoを採甚し始めたした。したがっお、次に怜蚎する候補はgo-swaggerでした。これは、swagger仕様からクラむアントずサヌバヌコヌドを生成できるツヌルです。明らかな欠点は、Goのコヌドしか生成しないこずです。実際、goshコヌド生成を䜿甚し、go-swaggerを䜿甚するずテンプレヌトを柔軟に操䜜できるため、理論的にはPHPコヌドの生成に䜿甚できたすが、ただ詊しおいたせん。



Go-swaggerは、トランスポヌトレむダヌの生成だけではありたせん。実際、それはアプリケヌションスケルトンを生成したす。ここで、DCの開発文化に぀いお少し觊れおおきたいず思いたす。内郚゜ヌスがありたす。぀たり、どのチヌムのどの開発者も、私たちが持っおいるすべおのサヌビスぞのプルリク゚ストを䜜成できたす。このようなスキヌムが機胜するために、開発におけるアプロヌチを暙準化しようずしおいたす。䞀般的な甚語、ロギング、メトリック、䟝存関係の操䜜、そしおもちろんプロゞェクト構造ぞの単䞀のアプロヌチを䜿甚したす。



このように、go-swaggerを実装するこずにより、Goでサヌビスを開発するための暙準を導入しおいたす。これは、圓初は予期しおいなかった目暙に向けたもう1぀のステップですが、開発党般にずっお重芁です。



最初のステップ



だから、行く-闊歩は、私たちのほずんどカバヌするこずができるように思われる興味深い候補であるこずが刀明したかったの芁件を。

泚すべおの曎なるコヌドはバヌゞョン0.24.0に関連する、むンストヌル手順はで芋぀けるこずができる䟋で私たちのリポゞトリ、および公匏サむトでは、珟圚のバヌゞョンのむンストヌル手順に぀いお説明しおいたす。
圌に䜕ができるか芋おみたしょう。スワガヌスペックを取り、サヌビスを生成したしょう



> goswagger generate server \
    --with-context -f ./swagger-api/swagger.yml \
    --name example1


以䞋を取埗したした







Makefileずgo.mod私はすでに自分で䜜成したした。



実際、swaggerで説明されおいるリク゚ストを凊理するサヌビスになりたした。



> go run cmd/example1-server/main.go
2020/02/17 11:04:24 Serving example service at http://127.0.0.1:54586
 
 
 
> curl http://localhost:54586/hello -i
HTTP/1.1 501 Not Implemented
Content-Type: application/json
Date: Sat, 15 Feb 2020 18:14:59 GMT
Content-Length: 58
Connection: close
 
"operation hello HelloWorld has not yet been implemented"


ステップ2。テンプレヌティングを理解する



明らかに、私たちが生成したコヌドは、私たちが運甚䞭に芋たいものずはかけ離れおいたす。



アプリケヌションの構造に求めるもの



  • アプリケヌションを構成できたす。デヌタベヌスに接続するための蚭定を転送したり、HTTP接続のポヌトを指定したりできたす。
  • アプリケヌションの状態、デヌタベヌス接続などを栌玍するアプリケヌションオブゞェクトを遞択したす。
  • アプリケヌションのハンドラヌ関数を䜜成したす。これにより、コヌドの䜜業が簡玠化されたす。
  • メむンファむルの䟝存関係を初期化したすこの䟋では、これは発生したせんが、それでも必芁です。


新しい問題を解決するために、いく぀かのテンプレヌトをオヌバヌラむドできたす。これを行うには、私が行ったようにGithub、次のファむルに぀いお説明したす。サヌビスを生成するための







テンプレヌトファむル`*.gotmpl`ず構成甚のファむルを説明する必芁があり`*.yml`たす。



次に、私が䜜成したテンプレヌトを順番に分析したす。ゎヌ闊歩ドキュメントはかなりたずえば、詳述されおいるので、私は、圌らず協力に深く朜るたせんここにある蚭定ファむルの蚘述が。Go-templatingが䜿甚されおいるこずに泚意しおください。これに぀いお既に経隓がある堎合、たたはHELM構成に぀いお説明する必芁がある堎合は、それを理解するのは難しくありたせん。



アプリケヌションの構成



config.gotmplには、1぀のパラメヌタヌアプリケヌションが着信HTTP芁求をリッスンするポヌトを持぀単玔な構造が含たれおいたす。たたInitConfig、環境倉数を読み取り、この構造を埋める関数も䜜成したした。main.goから呌び出すのでInitConfig、パブリック関数にしたした。



package config
 
import (
    "github.com/pkg/errors"
    "github.com/vrischmann/envconfig"
)
 
// Config struct
type Config struct {
    HTTPBindPort int `envconfig:"default=8001"`
}
 
// InitConfig func
func InitConfig(prefix string) (*Config, error) {
    config := &Config{}
    if err := envconfig.InitWithPrefix(config, prefix); err != nil {
        return nil, errors.Wrap(err, "init config failed")
    }
 
    return config, nil
}


このテンプレヌトをコヌドの生成時に䜿甚するには、YML構成でテンプレヌトを指定する必芁がありたす。



layout:
  application:
    - name: cfgPackage
      source: serverConfig
      target: "./internal/config/"
      file_name: "config.go"
      skip_exists: false


パラメヌタに぀いお少し説明したす。



  • name -玔粋に有益な機胜があり、生成には圱響したせん。
  • source-実際には、camelCaseのテンプレヌトファむルぞのパス、぀たり serverConfigは./server/config.gotmplず同等です。
  • target-生成されたコヌドが保存されるディレクトリ。ここでは、テンプレヌトを䜿甚しおパスを動的に生成できたす䟋。
  • file_name -生成されたファむルの名前。ここではテンプレヌトを䜿甚するこずもできたす。
  • skip_exists-ファむルが1回だけ生成され、既存のファむルを䞊曞きしないこずを瀺したす。構成ファむルはアプリケヌションの成長に応じお倉曎され、生成されたコヌドに䟝存するべきではないため、これは私たちにずっお重芁です。


コヌド生成構成では、オヌバヌラむドするファむルだけでなく、すべおのファむルを指定する必芁がありたす。倉曎しないファむルの堎合、たずえば、ここでsource指摘する意味の範囲内で。ちなみに、オリゞナルのテンプレヌトをご芧になりたい方はこちら。asset:< >asset:serverConfigureapi



アプリケヌションオブゞェクトずハンドラヌ



状態やデヌタベヌス接続などを保存するためのアプリケヌションオブゞェクトに぀いおは説明したせん。すべおが䜜成したばかりの構成に䌌おいたす。しかし、ハンドラヌを䜿甚するず、すべおがもう少し興味深いものになりたす。私たちの䞻な目暙は、仕様にURLを远加するずきに別のファむルにスタブ関数を䜜成するこずです。最も重芁なこずは、サヌバヌがこの関数を呌び出しお芁求を凊理するこずです。



関数テンプレヌトずスタブに぀いお説明したしょう。



package app
 
import (
    api{{ pascalize .Package }} "{{.GenCommon.TargetImportPath}}/{{ .RootPackage }}/operations/{{ .Package }}"
    "github.com/go-openapi/runtime/middleware"
)
 
func (srv *Service){{ pascalize .Name }}Handler(params api{{ pascalize .Package }}.{{ pascalize .Name }}Params{{ if .Authorized }}, principal api{{ .Package }}.{{ if not ( eq .Principal "interface{}" ) }}*{{ end }}{{ .Principal }}{{ end }}) middleware.Responder {
    return middleware.NotImplemented("operation {{ .Package }} {{ pascalize .Name }} has not yet been implemented")
}


䟋を詳しく芋おみたしょう。



  • pascalize--CamelCase他の機胜の説明はこちらで䞀線を画したす。
  • .RootPackage -生成されたWebサヌバヌパッケヌゞ。
  • .Package-生成されたコヌド内のパッケヌゞの名前。HTTP芁求ず応答に必芁なすべおの構造を蚘述したす。構造。たずえば、リク゚スト本文の構造やレスポンス構造などです。
  • .Name-ハンドラヌの名前。指定されおいる堎合は、仕様のoperationIDから取埗されたす。operationIDより明癜な結果を埗るために、垞に指定するこずをお勧めしたす。


ハンドラヌの構成は次のずおりです。



layout:
  operations:
    - name: handlerFns
      source: serverHandler
      target: "./internal/app"
      file_name: "{{ (snakize (pascalize .Name)) }}.go"
      skip_exists: true


ご芧のずおり、ハンドラヌコヌドは䞊曞きされずskip_exists: true、ファむル名はハンドラヌ名から生成されたす。



さお、スタブ関数がありたすが、Webサヌバヌは、これらの関数を䜿甚しお芁求を凊理する必芁があるこずをただ認識しおいたせん。main.goでこれを修正したしたコヌド党䜓を提䟛するわけではありたせん。完党版はここにありたす



package main
 
{{ $name := .Name }}
{{ $operations := .Operations }}
import (
    "fmt"
    "log"
 
    "github.com/delivery-club/go-swagger-example/{{ dasherize .Name }}/internal/generated/restapi"
    "github.com/delivery-club/go-swagger-example/{{ dasherize .Name }}/internal/generated/restapi/operations"
    {{range $index, $op := .Operations}}
        {{ $found := false }}
        {{ range $i, $sop := $operations }}
            {{ if and (gt $i $index ) (eq $op.Package $sop.Package)}}
                {{ $found = true }}
            {{end}}
        {{end}}
        {{ if not $found }}
        api{{ pascalize $op.Package }} "{{$op.GenCommon.TargetImportPath}}/{{ $op.RootPackage }}/operations/{{ $op.Package }}"
        {{end}}
    {{end}}
 
    "github.com/go-openapi/loads"
    "github.com/vrischmann/envconfig"
 
    "github.com/delivery-club/go-swagger-example/{{ dasherize .Name }}/internal/app"
)
 
func main() {
    ...
    api := operations.New{{ pascalize .Name }}API(swaggerSpec)
 
    {{range .Operations}}
    api.{{ pascalize .Package }}{{ pascalize .Name }}Handler = api{{ pascalize .Package }}.{{ pascalize .Name }}HandlerFunc(srv.{{ pascalize .Name }}Handler)
    {{- end}}
    ...
}


むンポヌトのコヌドは耇雑に芋えたすが、実際にはGo-templatingずgo-swaggerリポゞトリからの構造にすぎたせん。たた、関数でmainは、生成された関数をハンドラヌに割り圓おるだけです。



構成を瀺すコヌドを生成する必芁がありたす。



> goswagger generate server \
        -f ./swagger-api/swagger.yml \
        -t ./internal/generated -C ./swagger-templates/default-server.yml \
        --template-dir ./swagger-templates/templates \
        --name example2


最終結果は、リポゞトリで衚瀺できたす。



私たちが埗たもの



  • アプリケヌション、構成、および必芁なものに構造を䜿甚できたす。最も重芁なこずは、生成されたコヌドに埋め蟌むのがかなり簡単なこずです。
  • 個々のファむルの名前に至るたで、プロゞェクトの構造を柔軟に管理できたす。
  • テンプレヌティングは耇雑に芋え、慣れるたでに時間がかかりたすが、党䜓ずしおは非垞に匷力なツヌルです。


ステップ3。クラむアントの生成



Go-swaggerを䜿甚するず、他のGoサヌビスが䜿甚できるサヌビスのクラむアントパッケヌゞを生成するこずもできたす。ここでは、コヌド生成に぀いお詳しくは説明したせん。アプロヌチは、サヌバヌ偎のコヌドを生成する堎合ずたったく同じです。



Goプロゞェクトの堎合、パブリックパッケヌゞをに入れるのが通䟋./pkgです。同じこずを行いたす。サヌビスのクラむアントをpkgに入れ、次のようにコヌド自䜓を生成したす。



> goswagger generate client -f ./swagger-api/swagger.yml -t ./pkg/example3


生成されたコヌドの䟋はここにありたす。



これで、サヌビスのすべおの利甚者は、たずえばタグを䜿甚しお、このクラむアントを自分でむンポヌトできたす私の䟋では、タグはになりたすexample3/pkg/example3/v0.0.1。



クラむアントテンプレヌトは、たずえば、open tracing idコンテキストからヘッダヌぞのフロヌにカスタマむズできたす。



結論



圓然、内郚実装は、䞻に内郚パッケヌゞの䜿甚ずCIぞのアプロヌチさたざたなテストずリンタヌの実行が原因で、ここに瀺されおいるコヌドずは異なりたす。箱から出しお生成されたコヌドでは、技術メトリックの収集、構成の操䜜、およびロギングが構成されおいたす。すべおの䞀般的なツヌルを暙準化したした。このため、開発党般、特に新しいサヌビスのリリヌスを簡玠化し、補品に展開する前にサヌビスチェックリストをより迅速に通過できるようにしたした。



最初の目暙を達成したかどうかを確認したしょう。



  1. サヌビスに぀いお説明されおいる契玄の関連性を確認したす。これにより、新しいサヌビスの導入が加速され、チヌム間のコミュニケヌションが簡玠化されたす-はい。
  2. HTTP ( event streaming) — .
  3. , .. Inner Source — .
  4. , — ( — Bitbucket).
  5. , — ( , , ).
  6. Go — ( ).


泚意深い読者はおそらくすでに質問をしおいるでしょうテンプレヌトファむルはどのように私たちのプロゞェクトに入るのですか珟圚、各プロゞェクトにそれらを保存しおいたす。これにより、日垞の䜜業が簡玠化され、特定のプロゞェクトに合わせお䜕かをカスタマむズできたす。しかし、コむンには別の偎面がありたす。䞻にCIに関連する、テンプレヌトの集䞭曎新ず新機胜の提䟛のためのメカニズムがありたせん。



PSこの資料が気に入った堎合は、将来、サヌビスの暙準アヌキテクチャに関する蚘事を䜜成し、Goでサヌビスを開発するずきに䜿甚する原則に぀いお説明したす。



All Articles