cubenet_backend/MICROSERVICE_GUIDE.md

8.7 KiB
Raw Permalink Blame History

Руководство по созданию микросервиса

🚀 Быстрый старт

Шаг 1: Скопировать шаблон

cp -r microservices/template_service microservices/my_new_service
cd microservices/my_new_service

Шаг 2: Выбрать порт

Выберите порт из диапазона 13000-14000. Уже занятые:

  • 13001 - User Service

Пусть ваш сервис будет на порту 13002.

Шаг 3: Создать proto файл

Создайте shared_proto/proto/my_service.proto:

syntax = "proto3";

package my_service;

message GetDataRequest {
  string id = 1;
}

message Data {
  string id = 1;
  string content = 2;
}

service MyService {
  rpc GetData(GetDataRequest) returns (Data);
}

Шаг 4: Обновить shared_proto/src/lib.rs

Добавьте:

pub mod my_service {
    tonic::include_proto!("my_service");
}

Шаг 5: Обновить shared_proto/build.rs

Добавьте:

tonic_build::compile_protos("proto/my_service.proto")?;

Шаг 6: Обновить microservices/my_new_service/Cargo.toml

[package]
name = "my_new_service"
version.workspace = true
edition.workspace = true
authors.workspace = true

[dependencies]
tokio = { version = "1.35", features = ["full"] }
axum = "0.7"
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
tower = "0.4"
tower-http = { version = "0.5", features = ["trace"] }
tracing = "0.1"
tracing-subscriber = "0.3"
tonic = "0.11"
prost = "0.12"
shared_proto = { path = "../../shared_proto" }

Шаг 7: Реализовать сервис

Обновите microservices/my_new_service/src/main.rs:

use tonic::{Request, Response, Status, transport::Server};
use shared_proto::my_service::{
    my_service_server::{MyService, MyServiceServer},
    GetDataRequest, Data,
};
use tracing_subscriber;

pub struct MyServiceImpl;

#[tonic::async_trait]
impl MyService for MyServiceImpl {
    async fn get_data(
        &self,
        request: Request<GetDataRequest>,
    ) -> Result<Response<Data>, Status> {
        let id = request.into_inner().id;
        tracing::info!("GetData called with id: {}", id);

        Ok(Response::new(Data {
            id: id.clone(),
            content: format!("Data for {}", id),
        }))
    }
}

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    tracing_subscriber::fmt::init();

    let addr = "127.0.0.1:13002".parse()?;
    let my_service = MyServiceImpl;

    tracing::info!("MyService gRPC server listening on {}", addr);

    Server::builder()
        .add_service(MyServiceServer::new(my_service))
        .serve(addr)
        .await?;

    Ok(())
}

Шаг 8: Добавить в workspace

Обновите корневой Cargo.toml:

[workspace]
members = [
    "api_gateway",
    "api",
    "microservices/user_service",
    "microservices/my_new_service",
    "shared_proto"
]

Шаг 9: Скомпилировать

cargo check
# или
cargo build

📚 Примеры

Пример 1: Сервис продуктов

shared_proto/proto/product.proto:

syntax = "proto3";

package product;

message Product {
  int32 id = 1;
  string name = 2;
  float price = 3;
}

message GetProductRequest {
  int32 id = 1;
}

message CreateProductRequest {
  string name = 1;
  float price = 2;
}

service ProductService {
  rpc GetProduct(GetProductRequest) returns (Product);
  rpc CreateProduct(CreateProductRequest) returns (Product);
}

microservices/product_service/src/main.rs:

use tonic::{Request, Response, Status, transport::Server};
use shared_proto::product::{
    product_service_server::{ProductService, ProductServiceServer},
    GetProductRequest, Product, CreateProductRequest,
};
use tracing_subscriber;

pub struct ProductServiceImpl;

#[tonic::async_trait]
impl ProductService for ProductServiceImpl {
    async fn get_product(
        &self,
        request: Request<GetProductRequest>,
    ) -> Result<Response<Product>, Status> {
        let id = request.into_inner().id;
        
        Ok(Response::new(Product {
            id,
            name: "Product".to_string(),
            price: 99.99,
        }))
    }

    async fn create_product(
        &self,
        request: Request<CreateProductRequest>,
    ) -> Result<Response<Product>, Status> {
        let req = request.into_inner();
        
        Ok(Response::new(Product {
            id: 1,
            name: req.name,
            price: req.price,
        }))
    }
}

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    tracing_subscriber::fmt::init();

    let addr = "127.0.0.1:13002".parse()?;
    let service = ProductServiceImpl;

    tracing::info!("Product Service listening on {}", addr);

    Server::builder()
        .add_service(ProductServiceServer::new(service))
        .serve(addr)
        .await?;

    Ok(())
}

Пример 2: Сервис заказов

Использует User Service и Product Service:

microservices/order_service/src/main.rs:

use tonic::transport::Channel;
use shared_proto::order::{
    order_service_server::{OrderService, OrderServiceServer},
    CreateOrderRequest, Order,
};
use shared_proto::user::user_service_client::UserServiceClient;
use shared_proto::product::product_service_client::ProductServiceClient;

pub struct OrderServiceImpl {
    user_client: UserServiceClient<Channel>,
    product_client: ProductServiceClient<Channel>,
}

#[tonic::async_trait]
impl OrderService for OrderServiceImpl {
    async fn create_order(
        &self,
        request: tonic::Request<CreateOrderRequest>,
    ) -> Result<tonic::Response<Order>, tonic::Status> {
        let req = request.into_inner();
        
        // Вызвать User Service
        // Вызвать Product Service
        // Создать заказ
        
        Ok(tonic::Response::new(Order {
            id: 1,
            user_id: req.user_id,
            product_id: req.product_id,
        }))
    }
}

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    tracing_subscriber::fmt::init();

    let user_channel = Channel::from_static("http://127.0.0.1:13001")
        .connect()
        .await?;
    let product_channel = Channel::from_static("http://127.0.0.1:13002")
        .connect()
        .await?;

    let user_client = UserServiceClient::new(user_channel);
    let product_client = ProductServiceClient::new(product_channel);

    let order_service = OrderServiceImpl {
        user_client,
        product_client,
    };

    let addr = "127.0.0.1:13003".parse()?;
    tracing::info!("Order Service listening on {}", addr);

    tonic::transport::Server::builder()
        .add_service(OrderServiceServer::new(order_service))
        .serve(addr)
        .await?;

    Ok(())
}

🔄 Интеграция с API Gateway

После создания сервиса, обновите api_gateway/src/main.rs для добавления REST endpoints:

// Создать gRPC клиента для вашего сервиса
let my_service_channel = Channel::from_static("http://127.0.0.1:13002")
    .connect()
    .await?;

// Добавить REST endpoints
.route("/api/data/:id", get(get_data_handler))

Чек-лист

  • Скопирован шаблон сервиса
  • Выбран порт (13000-14000)
  • Создан proto файл
  • Обновлен shared_proto/src/lib.rs
  • Обновлен shared_proto/build.rs
  • Обновлен Cargo.toml микросервиса
  • Реализована логика сервиса
  • Добавлен в workspace Cargo.toml
  • Успешно компилируется (cargo check)
  • Добавлены тесты (опционально)

🐛 Отладка

Проверить что сервис запускается

cargo run -p my_new_service

Должно вывести:

MyService gRPC server listening on 127.0.0.1:13002

Проверить gRPC endpoint

# Установить grpcurl если не установлен
# https://github.com/fullstorydev/grpcurl

grpcurl -plaintext localhost:13002 list

Логирование

RUST_LOG=debug cargo run -p my_new_service

📞 Помощь

Если возникли проблемы:

  1. Проверьте что все proto файлы скомпилировались
  2. Убедитесь что порт не занят другим процессом
  3. Проверьте логи сервиса
  4. Обратитесь к ARCHITECTURE.md для общей информации