protobuf namspace collision in MSA (golang ent)

dasd412·2024년 7월 28일
0

실무 문제 해결

목록 보기
2/17

문제 상황

기능 작성을 완료한 zzz 서버를 시작하려 했는데, 다음 에러가 발생했다.

panic: proto: file "entpb/entpb.proto" is already registered
        previously from:"/server/services/xxx/pkg/ent/proto/entpb"
        currently from:  "/server/services/yyy/pkg/ent/proto/entpb"
See https://protobuf.dev/reference/go/faq#namespace-conflict

zzz 서버는 xxx 서버와 yyy 서버의 protobuf가 필요한 상황이다.
이전까지 겪지 않다가 처음으로 겪어본 문제였다. 왜냐하면 이전까지는 한 서버는 다른 서버 하나의 protobuf만 필요했었지만, 이번에 처음으로 2개 이상의 protobuf가 필요하게 됬기 때문이다.


자료

공식 protobuf 자료

  1. https://protobuf.dev/reference/go/faq/#namespace-conflict

  2. https://protobuf.dev/reference/go/go-generated/

위 자료 중, 2번 자료가 중요하다. 특히 다음 구절...

There is no correlation between the Go import path and the package specifier in the .proto file.
The latter is only relevant to the protobuf namespace,
while the former is only relevant to the Go namespace.
Also, there is no correlation between the Go import path and the .proto import path.

프로토콜 버퍼 컴파일러인 protoc 입장에서는 golang import path는 알 바 아니고, .proto 파일의 import path가 중요하다.

즉, 다음 두 경로는 golang import path는 다르지만, .proto 파일의 import path는 똑같은 entpb이기 때문에 충돌이 발생하는 것이다.

"/server/services/xxx/pkg/ent/proto/entpb"
"/server/services/yyy/pkg/ent/proto/entpb"


해결

entproto 자료

https://pkg.go.dev/entgo.io/contrib/entproto@v0.6.0#section-readme

golang ORM으로 entgo를 사용하고 있다. 그리고 스키마 생성할 때 자동으로 protobuf를 생성할 수 있도록 entproto를 같이 사용 중이다.

해결 방법은 다음과 같다.

1. ent/schema에 PackageName추가 하기

func (MessageWithPackageName) Annotations() []schema.Annotation {
	return []schema.Annotation{entproto.Message(
		entproto.PackageName("something"),
	)}
}

위처럼 작성하게 되면 .proto 파일이 PackageName에 기입된 디렉토리 내에 위치하게 된다. 뿐만 아니라 .protoentproto.proto가 아니게 된다.

위 코드를 예로 들면, entpb/something 패키지 내에 something.proto가 생긴다.

2. .proto 파일 위치 변경하기 + generate.go 생략하기

go generate 라는 명령어를 수행하게 되면, generate.go가 자동으로 생성된다. 또한 .proto 파일 역시 아직 1.의 packageName에 종속된다.

.proto를 원하는 디렉토리로 위치시키고, generate.go를 생성하지 않게 하려면 다음과 같이 entc.go를 수정하면 된다.

//go:build ignore

package main

import (
	"entgo.io/contrib/entproto"
	"log"

	"entgo.io/contrib/entgql"
	"entgo.io/ent/entc"
	"entgo.io/ent/entc/gen"
)

func main() {
	ex, err := entgql.NewExtension(
		
	)
	entProtoExtension, err := entproto.NewExtension(
		entproto.SkipGenFile(),
		entproto.WithProtoDir(
			"../../something/",
		),
	)

	opts := []entc.Option{
		entc.Extensions(
			ex,
			entProtoExtension,
		),
	}
	if err := entc.Generate("./schema", &gen.Config{}, opts...); err != nil {
		log.Fatalf("running ent codegen: %v", err)
	}
}

entproto.SkipGenFile()를 활용하면, .proto를 생성하는generate.go가 자동 생성되는 걸 막을 수 있다. 단, 이 기능을 활용하려면 최신 버전의 entproto가 필요하다.

entproto.WithProtoDir()를 활용하면, .proto가 생성되는 위치를 프로그래머 마음대로 할 수 있게 된다.

3. protoc 컴파일 명령어 수정

다음은 makefile에 작성된 명령어 집합이다.

./pkg/ent/schema에서 스키마를 읽어온 후, PROTOBUF_DIR에 존재하는 ~~.proto를 이용하여 컴파일을 실행한다. 그리고 OUT_DIR에 grpc용 코드, grpc 서비스용 코드, .go 코드를 생성해준다.

PROTOBUF_DIR=../../protobuf
OUT_DIR=pkg/ent/proto/entpb

proto:
	@mkdir -p $(OUT_DIR)
	@find $(PROTOBUF_DIR)/xxx -name '*.proto' | xargs -I {} protoc -I=$(PROTOBUF_DIR) --go_out=$(OUT_DIR) --go_opt=paths=source_relative \
                                                            --go-grpc_out=$(OUT_DIR) --go-grpc_opt=paths=source_relative {} \
                                                            --entgrpc_out=$(OUT_DIR) --entgrpc_opt=paths=source_relative,schema_path=./pkg/ent/schema

profile
시스템 아키텍쳐 설계에 관심이 많은 백엔드 개발자입니다. (Go/Python/MSA/graphql/Spring)

0개의 댓글