Go 프로젝트

프로젝트 시작하기

우선 프로젝트 시작하기 문서를 따라 새로운 프로젝트를 생성한다.

디펜던시 불러오기

dep을 사용해서 필요한 디펜던시를 초기화한다.

dep init

이후 디펜던시 변경이 있을 경우 dep ensure를 통해서 디펜던시를 업데이트 할 수 있다.

프로젝트 레이아웃(Project Layout)

Go 프로젝트 레이아웃은 기본적으로 golang-standards/project-layout 레포지토리의 정의를 참고한다.

디렉토리 설명
/cmd 프로젝트의 주요 어플리케이션을 포함한다. /cmd 안의 디렉토리 명은 실행할 파일 이름과 동일하게 구성한다. 서버를 실행시키거나 데이터베이스 연결을 맺는 등의 역할을 수행한다.
/internal 프로젝트 외부에 공개되지 않는 라이브러리 코드는 이 곳에 위치한다. 서비스 프로젝트의 경우 대부분의 코드가 이 안에 위치한다.
/internal/app 실제 어플리케이션 코드를 포함한다. 프레임워크를 포함하는 프레젠테이션 영역은 보통 이 디렉토리 안에 포함된다.
/internal/pkg 앱 사이에 공유되는 라이브러리 코드를 포함한다. 도메인 영역과 데이터 영역은 보통 이 디렉토리 안에 포함된다.
/pkg 외부로 공개하는 라이브러리 코드를 포함한다.
/vendor 프로젝트가 사용하는 외부 라이브러리를 포함한다. 일반적으로 dep 을 사용해 관리된다.

API 구현

API 정의 문서를 통해 정의된 API의 기능을 구현한다.

라이브러리 적용

API 정의를 통해 빌드된 Go 라이브러리는 buzzapis 레포지토리의 go 폴더 안에 생성된다. Dep 등의 의존성 도구를 통해서 필요한 API 라이브러리를 가져올 수 있다.

  1. API 라이브러리를 임포트(import)한다.

    // e.g main.go
    import "github.com/Buzzvil/buzzapis/go/library"
    
  2. Dep을 통해 라이브러리를 가져온다.

    $ dep ensure
    

인터페이스 구현

라이브러리에 정의된 API를 구현함으로써 클라이언트에게 서비스를 제공할 수 있다.

package librarysrv

import (
    pb "github.com/Buzzvil/buzzapis/go/library"
)

type server struct {
}

// New creates library service server.
func New(db *sql.DB) pb.LibraryServiceServer {
    return &server{}
}

func (s *server) GetBook(c context.Context, r *pb.GetBookRequest) (*pb.Book, error) {
    // TODO: Find the book from database.
    return &pb.Book{Title: "Title here.", Author: "Author here."}, nil
}

서버 구성

서버 생성 및 실행

gRPC를 통해 서버를 생성하고 실행할 수 있다. 이때 main.go 에서는 연결을 맺거나 서버를 실행하는 역할에만 집중하고 실제 구현은 내부 패키지에게 위임한다. 자세한 사용법은 Starting the go server 문서를 참고한다.

// cmd/librarysvc/main.go
package librarysvc

import (
    "log"
    "net"
    "os"

    pb "github.com/Buzzvil/buzzapis/go/library"
    "github.com/Buzzvil/librarysvc/internal/app/librarysrv"
    "google.golang.org/grpc"
)

func main() {
    lis, err := net.Listen("tcp", fmt.Sprintf(":%s", os.Getenv("PORT")))
    if err != nil {
        log.Fatalf("Failed to listen: %v", err)
    }
    s := grpc.NewServer()
    pb.RegisterLibraryServiceServer(s, librarysrv.New())
    if err := s.Serve(lis); err != nil {
        log.Fatalf("Failed to serve: %v", err)
    }
}

서버 로깅

logrus 등의 라이브러리를 통해 로깅 관련 옵션을 설정할 수 있다. 자세한 사용법은 logrus 레포지토리를 확인한다.

// cmd/librarysvc/main.go
package librarysvc

import (
    "github.com/grpc-ecosystem/go-grpc-middleware"
    "github.com/grpc-ecosystem/go-grpc-middleware/logging/logrus"
    "github.com/grpc-ecosystem/go-grpc-middleware/tags"
    "github.com/sirupsen/logrus"
)

func newGrpcServer() *grpc.Server {
    logrusEntry := logrus.NewEntry(logrus.StandardLogger())
    opts := []grpc_logrus.Option{}
    grpc_logrus.ReplaceGrpcLogger(logrusEntry)
    return grpc.NewServer(
        grpc_middleware.WithUnaryServerChain(
            grpc_ctxtags.UnaryServerInterceptor(grpc_ctxtags.WithFieldExtractor(grpc_ctxtags.CodeGenRequestFieldExtractor)),
            grpc_logrus.UnaryServerInterceptor(logrusEntry, opts...),
        ),
        grpc_middleware.WithStreamServerChain(
            grpc_ctxtags.StreamServerInterceptor(grpc_ctxtags.WithFieldExtractor(grpc_ctxtags.CodeGenRequestFieldExtractor)),
            grpc_logrus.StreamServerInterceptor(logrusEntry, opts...),
        ),
    )
}