Go CLI에서 플러그인 메커니즘을 만드는 방법 n 선택

이 글은 Go 3 Advent Calendar 2020 25일째 되는 글이다.2018년부터 3년 연속 이어진 고 25일째다.나는 크리스마스를 잘한다.
편리한 도구, 특히 특정한 도구 유형을 조작하거나 생성하는 도구는 대상물의 종류를 늘릴 때 플러그인 기구를 만들고 싶은 경우가 많다.이럴 때 우리는 세상의 도구, 특히 Go제의 도구가 어떤 수법을 사용했는지에 대해 간단한 조사를 하고 정리했다.
2년 전 고콘에서 비슷한 내용을 이야기한 적이 있는데 플러그인 기구를 만드는 동기 등은 슬라이드를 참조할 수 있다.
https://speakerdeck.com/izumin5210/consider-pluggable-cli-tool-implementation-number-gocon

별점 항목


또한 슬라이드에서 플러그인 기구 자체의 도입 동기를 언급했는데 우리는'플러그인 기구의 도입이 UX를 악화시킬 수 있는지','플러그인의 개발 체험이 어떠한가'라는 두 가지 큰 프로젝트에서 볼 것이다.

  • User Experience: 도구 이용자가 편안하고 최소한의 사전 지식을 사용할 수 있는지

  • Plugin Developer Experience: 설치, 디버깅, 배포 용이
  • 실현 모드


    표준plugen


    우선 Go를 기준으로 배치된 기구(이것을 언급하는 것을 잊고 GoCon으로 토로한다).
    https://golang.org/pkg/plugin/
    사용 방법이 매우 간단하다.플러그인main은 패키지에서 Export 변수 함수를 사용하여 -buildmode=plugin 옵션으로 구성됩니다.
    // https://golang.org/pkg/plugin/#Symbol より
    
    package main
    
    import "fmt"
    
    func F() { fmt.Println("Hello, world\n") }
    
    구축된 플러그인을 옆으로 읽고 Lookup에서 Export 기호를 픽업한 후 사용합니다.
    package main
    
    import "plugin"
    
    func main() {
    	p, err := plugin.Open("plugin.so")
    	// error handling
    	f, err := p.Lookup("F")
    	// error handling
    	f.(func())() // prints "Hello, world"
    }
    
    이 기준의 plugin 포장에 대해 제가 방금 당신에게 준 평가 항목을 고려해 보겠습니다.

  • User Experience: 현재
  • 플러그인을 설치하는 메커니즘을 고려하지 않으면 어렵다
  • 요점
  • 이용자는 도구 옆에 규정된 규칙에 따라 설정해야 한다.so
  • 가능한 구조
  • 플러그인의 URL을 도구에 전달하면 다운로드하여 적절한 위치에 놓을 수 있음
  • 플러그인을 제공하는 등록표
  • 플러그인에 설치할 수 있는 스크립트 공개하기(cf.goreleaser)

  • Developer Experience: 일반
  • 도구에 규정된 서명만 준수하고 함수와 변수를 Export하면 개발 자체가 간단합니다
  • (당연한 거지만) 사인을 잘못하면 제대로 작동하지 않아
  • 개발 중에 서명을 잘못해도 알아볼 수 있는 API가 있었으면 좋겠어요?
  • Go의 함수와 변수는 직접 교환할 수 있고 자유도가 높다
  • 플러그인 등록표를 준비한 상황에서 도구에 로컬 야량 플러그인을 처리할 수 있는 메커니즘이 없으면 디버깅이 번거롭다
  • Plugins as executable files


    유명한 곳은kubectl의 플러그인 기구가 사용하는 모델이다."kubectl foo 실행 후 $PATH부터 kubectl-foo라는 파일을 찾아 실행합니다."실행할 수 있다면 뭐든지 돼. Go는커녕 조개 스크립트라도 문제없어.
    https://kubernetes.io/docs/tasks/extend-kubectl/kubectl-plugins/
    Protocol Buffers IDL을 컴파일하는 도구protoc도 플러그인을 실행 가능한 파일로 처리합니다.protoc 흥미로운 것은 인터페이스가 input/output에서 표준 입력과 출력을 이용하여protobuf의 2진법을 교환하는 것이다.
    https://github.com/protocolbuffers/protobuf/blob/v3.14.0/src/google/protobuf/compiler/plugin.proto#L67-L183
    SQLBoiler라는 ORM도 프로토크와 비슷한 수법을 적용했다.이쪽은 표준 입력과 출력으로 JSON을 교환합니다.DB의 모드에서 Struct와 조회 구축기의 코드를 생성하고 모든 DB의 adapter는 플러그인으로 임의의 RDB에 대응할 수 있습니다.
    https://github.com/volatiletech/sqlboiler

  • 차이가 많지 않다.
  • 실행 파일을 구성할 수만 있다면 되기 때문에 다른 장치를 타기가 쉽다
  • Homebrew로 설치 가능
  • 플러그인도 Go인 경우 go get에서 바이너리가 생성될 것으로 기대할 수 있음
  • gex처럼 Go Module에서 의존하는 2진법을 잘 사용하는 구조도 있다
  • 플러그 인이 Go인 경우 Go Module을 통해 플러그 인의 버전 고정 가능
  • 여러 사람이 개발한 프로젝트에서 플러그인 버전을 고정시키지 못하면 골치 아프다

  • Developer Experience: 노력
  • 테스트 자체는 간단합니다.
  • 보다 우선적으로 볼 수 있는 곳에서 파일을 구성하면 됩니다
  • .
  • 도구-플러그인 간의 교환을 어떻게 실현하는가에 중점을 두다
  • 양방향 교환이라면 표준 입력과 출력을 사용하는 것이 주류인가?
  • "플러그인은 특정 인터페이스를 만족시키는 대상만 함수에 전달할 수 있다면 플러그인은 상당히 간단하고 수정도 더욱 강해진다
  • qlboiler가 바로 이런 API입니다.
  • 그러나 이렇게 하면 플러그인의 설치 언어가 제한되어 있다
  • 대화에서 표준 입력과 출력을 사용하면 print debug와 debugger가 좀 번거롭다는 단점이 있는데...
  • Plugins as RPC server


    Terraform 등 이용hashicorp/go-plugin 플러그인에 사용되는 모델."Plugins as executable files"의 아종입니다. 이 플러그인은 RPC 서버로 RPC client에서 요청을 보내는 도구로 만들어진 구조입니다.
    https://github.com/hashicorp/go-plugin
    Go 2 Advent Calendar 2020째 날, @po3rin은 "go-plugin × gRPC로 자체 제작 Go 도구에 플러그인 기구를 설치하는 방법 - 호기심이 살해되었다."라는 글에서 우리에게 해설을 해 주었습니다. 자세한 내용은 저쪽을 보십시오.
    https://po3rin.com/blog/go-plug

  • User Experience: 대충대충
  • 여기서는 "Plugins as executable files"와 기본적으로 같아야 합니다

  • Develoepr Experience: 괜찮아 보입니다.
  • 지정된 협의를 말할 수 있었으면 좋겠다

  • gRPC를 사용할 수 있을 것 같아서 인터페이스도 잘 정해져 있고 설치도 편리하기 때문이다
  • 고콘에서 채팅할 때 몰랐는데 2017년에 가입한 것 같은데...
  • Pull Request 가져오기에서 장점과 배경을 상세히 설명
  • 더 많은 노력을 기울이면 도구 측면에서도 서버와 플러그인을 양방향으로 교환할 수 있다
  • CLI가 아닌 라이브러리로 제공되므로


    gqlgen가 채택한 모델.이것도 코드 생성기로서 Go의 GraphiQL 서버를 GraphiQL 모드에서 생성하는 골격이다.이것은 상당히 획기적인(?)플러그인은 특정 인터페이스를 실현하고 제공하기만 하면 된다.사용자가 gqlgen 설정을 불러오고 플러그인을 삽입해서 실행합니다.
    // +build ignore
    
    package main
    
    import (
    	// ...
    )
    
    func main() {
    	cfg, err := config.LoadConfigFromDefaultLocations()
    	// error handling
    
    	err = api.Generate(cfg,
    		api.AddPlugin(yourplugin.New()), // ← ここでプラグインを追加
    	)
    	// error handling
    }
    
    https://gqlgen.com/reference/plugins/
    이 기구의 위대함은'gqlgen은 Go가 있는 프로젝트에서만 사용한다'는 전제에서 gqlgen 자체의 개발자에게 가장 간단한 방법을 채택한 것이다.다만, $PATH에서 읽는 형식에 비해 User Experience보다 못할 수도 있지만 go generate 등에서 gqlgen라는 이름으로 바이너리를 호출하면 사용자도 기본적으로 신경 쓸 필요가 없다.
    // +build tools
    
    //go:generate go build -o ./bin/gqlgen ./cmd/gqlgen
    
    package tools
    

  • User Experience: (사전 요구 사항이 있을 경우) 좋음

  • Develoepr Experience: 최고
  • 공구가 준비한 인터페이스일 뿐이야!
  • 이외에도 gqlgen은'이진 정보를 발산하지 않는다(googet에서 구축된 것이기 때문에)','정적 파일을 하지 않는embed','생성된 코드를 편집할 수 있는 느낌을 다시 만들 수 있다'현실의 구분과 중요한 체험이 잘 주입된 균형이 참고가 된다.꼭 읽어주세요.

    총결산


    몇 개의 플러그인 기구의 설치 모델을 조사하고 고찰을 결합하여 총결하였다.이전 내용에 따르면 현재 자신에게'전제'가 있다면 gqlgen과 같은 패턴이고, 그렇지 않으면 hashicorp/go-pluggin을 사용하자.
    세상에는 이번에 소개한 것 외에도 다양한 실복 패턴이 있으니 꼭 조사해 보세요.저는 개인적으로create-react-app를 비교적 좋아합니다.

    좋은 웹페이지 즐겨찾기