В первой статье этой серии из трех статей пошагово создадим масштабируемое серверное приложение с Golang, gRPC и PostgreSQL, подключим модульные тесты, контейнеризируем приложение и задействуем Envoy как прокси-службу взаимодействия. В итоге спроектируем, выполним оценку и запустим современное серверное приложение.

Настройка Golang с gRPC и PostgreSQL

1. Структура проекта

Организуем проект, обеспечив ему модульность и удобство восприятия:

grpc-app/
├── app/
├── dbconnections/ # Подключение к базе данных
├── models/ # Структура данных моделей
├── proto/ # proto-файлы gRPC
├── generated/ # Генерируемый proto-код
├── service/ # Бизнес-логика
├── controller/ # Реализации запросов gRPC
├── types/ # Основные методы для доступных служб
├── utils/ # Вспомогательные функции
├── Dockerfile # Концигурация Docker
├── Docker-compose.yaml # Концигурация Docker
├── envoy.yaml # Концигурация Docker Envoy
├── main.go # Точка входа приложения
├── app_test.go # Модульные тесты

2. Добавление зависимостей

Сначала инициализируем проект с помощью go mod init <название проекта>, затем добавляем зависимости: grpc, gorm и postgres.

go mod init grpc-app
go get google.golang.org/grpc
go install google.golang.org/protobuf/cmd/protoc-gen-go@latest
go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest
go get -u gorm.io/gorm
go get -u gorm.io/driver/postgres

3. Определение proto-файлов

Файл буферов протокола является неотъемлемой частью protobuf, независимого от языков программирования и операционных систем фреймворка сериализации Google. В нем описываются структура данных (то есть сообщения) и службы (то есть методы RPC) для взаимодействия в системах на основе gRPC.

syntax = "proto3";

option go_package = "grpc-app/app/generated/buku";

message BukuResponse {
int32 id = 1;
string judul = 2;
string penulis = 3;
int32 kuantitas = 4;
string tempat_penyimpanan = 5;
}

message AddBukuRequest {
string judul = 1;
string penulis = 2;
int32 kuantitas = 3;
string tempat_penyimpanan = 4;
}

message EmptyRequest{

}

message EmptyResponse {
bool success = 1;
}

service BukuService {
rpc AddBuku(AddBukuRequest) returns (BukuResponse);
// Добавляется оставшаяся операция CRUD..
}

4. Генерирование proto-файлов

Командой make gen создаем makefile, в котором перечислены команды для компилирования и генерирования proto-файла:

gen:
@protoc \
--proto_path=protobuf \
--go_out=app/generated/buku --go_opt=paths=source_relative \
--go-grpc_out=app/generated/buku --go-grpc_opt=paths=source_relative \
app/proto/buku.proto

Реализация gRPC и PostgreSQL

1. Настройка подключения к PostgreSQL

package dbconnection

import (
"log"

"gorm.io/driver/postgres"
"gorm.io/gorm"
)

func StartDB() (*gorm.DB, error) {
dsn := "host=localhost user=postgres password=postgres dbname=database port=8080 sslmode=disable TimeZone=Asia/Jakarta"
db, err := gorm.Open(postgres.Open(dsn), &gorm.Config{})
if err != nil {
log.Fatal("failed to connect database", err)
} else {
log.Println("Connected to database")
}

return db, nil
}

2. Определение модели базы данных

package models

type Buku struct {
ID int32 `gorm:"primaryKey"` // Первичный ключ
Judul string `gorm:"not null"` // Название книги
Penulis string `gorm:"not null"` // Автор
Kuantitas int32 `gorm:"not null"` // Количество
TempatPenyimpanan string `gorm:"not null"` // Место хранения
}

func (Buku) TableName() string {
return "buku"
}

3. Интерфейс gRPC

Создаём интерфейс Golang, которым описываются методы, реализуемые службой для управления операциями.

package types

import (
"context"

"grpc-app/app/generated/buku"
)

type BukuService interface {
AddBuku(context.Context, models.Buku) error
GetAllBuku(context.Context) []*buku.BukuResponse
DeleteBuku(context.Context, *buku.BukuIdRequest) error
UpdateBuku(context.Context, *buku.UpdateBukuRequest) error
}

4. Services

В этом пакете пишем методы для выполнения CRUD-операций с GORM.

package services

import (
"context"
"grpc-app/app/dbconnection"
"grpc-app/app/models"
"grpc-app/app/generated/buku"
"log"

"gorm.io/gorm"
)

type BukuService struct {
db *gorm.DB
}

func NewBukuService() *BukuService {

db, err := dbconnection.StartDB()
if err != nil {
log.Fatalf("Failed to connect to the database: %v", err)
}
return &BukuService{db: db}
}

func (s *BukuService) AddBuku(ctx context.Context, buku models.Buku) error {
newBuku := models.Buku{
Judul: buku.Judul,
Penulis: buku.Penulis,
Kuantitas: buku.Kuantitas,
TempatPenyimpanan: buku.TempatPenyimpanan,
}

if err := s.db.Create(&newBuku).Error; err != nil {
log.Printf("Failed to add new book: %v", err)
return err
}
return nil
}
// Добавляются остальные методы CRUD..

5. Controller

В этом пакете пишем методы для обработки запроса, затем вызываем их в службе для выполнения CRUD-операций. Методом также создается и выводится ответное сообщение в соответствии с интерфейсом gRPC.

package controller

import (
"context"
"grpc-app/app/models"
"grpc-app/app/types"
"grpc-app/app/generated/buku"

"google.golang.org/grpc"
)

type BukuGrpcHandler struct {
bukuService types.BukuService
buku.UnimplementedBukuServiceServer
}

func NewGrpcBukuService(grpc *grpc.Server, bukuService types.BukuService) {
gRPCHandler := &BukuGrpcHandler{
bukuService: bukuService,
}

buku.RegisterBukuServiceServer(grpc, gRPCHandler)

}

func (h *BukuGrpcHandler) AddBuku(ctx context.Context, req *buku.AddBukuRequest) (*buku.BukuResponse, error) {
var idCounter int
add_buku := models.Buku{
ID: int32(idCounter),
Judul: req.Judul,
Penulis: req.Penulis,
Kuantitas: req.Kuantitas,
TempatPenyimpanan: req.TempatPenyimpanan,
}
idCounter++
err := h.bukuService.AddBuku(ctx, add_buku)
if err != nil {
return nil, err
}
res := &buku.BukuResponse{
Id: add_buku.ID,
Judul: add_buku.Judul,
Penulis: add_buku.Penulis,
Kuantitas: add_buku.Kuantitas,
TempatPenyimpanan: add_buku.TempatPenyimpanan,
}

return res, nil
}
// Добавляются остальные методы CRUD..

6. Создание сервера gRPC

Написав и определив все доступные службы gRPC, создаем и запускаем сервер gRPC в пакете main.

package main
import (
"grpc-app/app/dbconnection"
"grpc-app/app/controller"
"grpc-app/app/services"
"log"
"net"

"google.golang.org/grpc"
)
func main() {

db, err := dbconnection.StartDB()

if err != nil {
log.Fatal("failed to connect database", err)
} else {
log.Println("Connected to database")
}

StartGRPCServer()
StartGinServer()
}

func StartGRPCServer() {
portNumber := 9000
lis, err := net.Listen("tcp", ":"+portNumber)
if err != nil {
log.Fatalf("gRPC fail to listen on port "+portNumber+" : %v", err)
} else {
log.Println("gRPC Starting...")

grpcServer := grpc.NewServer()

bukuService := services.NewBukuService()
controller.NewGrpcBukuService(grpcServer, bukuService)

go func() {
log.Println("gRPC listening to port " + portNumber)
if err := grpcServer.Serve(lis); err != nil {
log.Fatalf("could not start grpc server: %v", err)
}
}()
}
}

func StartGinServer() {
hostName := localhost
portNumber := 9090

server := gin.Default()

server.Run(hostName + ":" + portNumber)
}

Запуск и тестирование приложения

Запускаем приложение командой go run main.go и тестируем службы при помощи Postman, grpcurl, BloomRPC и т. д. Воспользуемся BloomRPC:

Заключение

Созданием программы Go с gRPC и PostgreSQL обеспечивается высокоэффективная, масштабируемая архитектура для построения распределенных систем. Комбинируя высокопроизводительную, всеязычную RPC-платформу gRPC с надежной реляционной базой данных PostgreSQL, разработчики конструируют системы для обработки сложных моделей данных и эффективного функционирования при большой нагрузке.

С gRPC обеспечивается быстрое взаимодействие служб при поддержке двунаправленной потоковой передачи, аутентификации и автоматического генерирования кода для клиент-серверных взаимодействий. С PostgreSQL же обеспечиваются соответствие требованиям ACID, мощная поддержка запросов и возможность при необходимости масштабироваться.

Таким сочетанием гарантируется не только респонсивность и масштабируемость приложений, но и их сопровождаемость и безопасность. Комбинируя модель параллелизма Go с надежностью PostgreSQL, разработчики создают высокореспонсивные бэкенды, которые легко масштабируются и оптимизируются для повышения производительности.

В целом этой архитектурой обеспечивается прочная основа для создания современных микросервисных приложений, которые эффективно справляются со сложными задачами.

Читайте также:

Читайте нас в Telegram, VK и Дзен


Перевод статьи Izzan Alfadhil: Implementing gRPC and PostgreSQL in GO

Предыдущая статьяБесконечное количество репозиториев Git на Cloudflare Worker с быстрым развертыванием
Следующая статьяИскусственный интеллект и машинное обучение