백엔드 개발에서 마이크로서비스 아키텍처는 점점 더 많은 인기를 끌고 있습니다. 이 아키텍처는 서비스 간의 독립성과 확장성을 제공하여 각 서비스가 독립적으로 개발, 배포, 확장될 수 있도록 합니다. 하지만 마이크로서비스 간 통신은 이러한 아키텍처의 중요한 요소이며, 이를 효과적으로 구현하는 방법에 대해 고민해야 합니다. 이번 글에서는 Go 언어를 사용하여 마이크로서비스 간 통신 방법을 살펴보겠습니다.
1. 마이크로서비스 간 통신의 필요성
마이크로서비스는 여러 개의 독립적인 서비스로 구성되며, 이들 서비스는 서로 데이터를 주고받기 위해 통신해야 합니다. 통신 방식은 크게 두 가지로 나눌 수 있습니다.
- 동기 통신: 클라이언트가 요청을 보내면 서버가 응답을 주기까지 기다리는 방식. 일반적으로 HTTP REST API나 gRPC를 사용합니다.
- 비동기 통신: 요청을 보내고 응답을 기다리지 않는 방식. 메시지 큐(예: RabbitMQ, Kafka)를 사용하여 서비스 간에 메시지를 주고받습니다.
여기서는 Go 언어로 동기 통신을 구현하는 방법을 중심으로 살펴보겠습니다.
2. Go로 REST API 구현하기
Go 언어는 간결한 문법과 높은 성능 덕분에 마이크로서비스 개발에 적합합니다. REST API를 구현하기 위해 Go의 net/http 패키지를 사용할 수 있습니다.
2.1 간단한 REST API 예제
아래는 Go 언어로 작성한 간단한 REST API 서버입니다. 이 서버는 클라이언트로부터 JSON 형식의 데이터(사용자 정보)를 받아 이를 처리합니다.
package main
import (
"encoding/json"
"net/http"
)
// User 구조체 정의
type User struct {
ID int json:"id"
Name string json:"name"
}
// 사용자 정보를 저장할 슬라이스
var users []User
// 사용자 추가 핸들러
func addUser(w http.ResponseWriter, r http.Request) {
var newUser User
if err := json.NewDecoder(r.Body).Decode(&newUser); err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
users = append(users, newUser)
w.WriteHeader(http.StatusCreated)
}
// 사용자 목록 핸들러
func getUsers(w http.ResponseWriter, r http.Request) {
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(users)
}
func main() {
http.HandleFunc("/users", addUser)
http.HandleFunc("/users/list", getUsers)
http.ListenAndServe(":8080", nil)
}
위 코드는 /users 경로로 POST 요청을 보내 사용자 정보를 추가하고, /users/list 경로로 GET 요청을 보내 저장된 사용자 목록을 반환하는 간단한 API 서버입니다.
2.2 API 테스트
API를 테스트하기 위해 curl 명령어를 사용할 수 있습니다.
curl -X POST -H "Content-Type: application/json" -d '{"id":1,"name":"홍길동"}' http://localhost:8080/users
# 사용자 목록 조회
curl http://localhost:8080/users/list
3. gRPC를 이용한 통신
gRPC는 Google에서 개발한 고성능 RPC(Remote Procedure Call) 프레임워크로, Protocol Buffers를 사용하는 이점 덕분에 성능이 뛰어나고 다양한 언어 간 통신을 지원합니다. Go에서 gRPC를 사용하기 위해 필요한 패키지를 설치합니다.
go get google.golang.org/grpc
go get google.golang.org/protobuf
3.1 gRPC 서비스 정의
user.proto 파일을 생성하여 서비스와 메시지 타입을 정의합니다.
syntax = "proto3";
package user;
service UserService {
rpc AddUser(User) returns (Response);
rpc ListUsers(Empty) returns (UserList);
}
message User {
int32 id = 1;
string name = 2;
}
message Response {
string message = 1;
}
message UserList {
repeated User users = 1;
}
message Empty {}
3.2 gRPC 서버 구현
user_server.go 파일을 생성하여 gRPC 서버를 구현합니다.
package main
import (
"context"
"log"
"net"
"google.golang.org/grpc"
pb "path/to/your/proto"
)
type server struct{
pb.UnimplementedUserServiceServer
}
var users []pb.User
func (s server) AddUser(ctx context.Context, user pb.User) (pb.Response, error) {
users = append(users, user)
return &pb.Response{Message: "User added successfully!"}, nil
}
func (s server) ListUsers(ctx context.Context, empty pb.Empty) (*pb.UserList, error) {
return &pb.UserList{Users: users}, nil
}
func main() {
lis, err := net.Listen("tcp", ":50051")
if err != nil {
log.Fatalf("Failed to listen: %v", err)
}
s := grpc.NewServer()
pb.RegisterUserServiceServer(s, &server{})
if err := s.Serve(lis); err != nil {
log.Fatalf("Failed to serve: %v", err)
}
}
3.3 gRPC 클라이언트 구현
gRPC 클라이언트를 작성하여 서버와 통신합니다.
package main
import (
"context"
"log"
"time"
"google.golang.org/grpc"
pb "path/to/your/proto"
)
func main() {
conn, err := grpc.Dial("localhost:50051", grpc.WithInsecure())
if err != nil {
log.Fatalf("Did not connect: %v", err)
}
defer conn.Close()
client := pb.NewUserServiceClient(conn)
// 사용자 추가
ctx, cancel := context.WithTimeout(context.Background(), time.Second)
defer cancel()
user := &pb.User{Id: 1, Name: "홍길동"}
response, err := client.AddUser(ctx, user)
if err != nil {
log.Fatalf("Could not add user: %v", err)
}
log.Println(response.Message)
// 사용자 목록 요청
userList, err := client.ListUsers(ctx, &pb.Empty{})
if err != nil {
log.Fatalf("Could not list users: %v", err)
}
log.Println(userList)
}
위 코드는 gRPC 클라이언트를 생성하고, AddUser와 ListUsers 메서드를 호출하는 예제입니다.
마무리
마이크로서비스 간 통신은 시스템의 전체적인 성능과 확장성에 큰 영향을 미치기 때문에 신중하게 설계해야 합니다. Go 언어는 REST API와 gRPC를 모두 구현하기에 적합한 언어로, 각 통신 방식의 장단점을 잘 이해하고 상황에 맞게 선택하는 것이 중요합니다. 이번 글을 통해 Go로 마이크로서비스 간 통신을 구현하는 기본적인 방법을 익히셨길 바랍니다.