cubenet_backend/MICROSERVICE_GUIDE.md

375 lines
8.7 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Руководство по созданию микросервиса
## 🚀 Быстрый старт
### Шаг 1: Скопировать шаблон
```bash
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`:
```protobuf
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
Добавьте:
```rust
pub mod my_service {
tonic::include_proto!("my_service");
}
```
### Шаг 5: Обновить shared_proto/build.rs
Добавьте:
```rust
tonic_build::compile_protos("proto/my_service.proto")?;
```
### Шаг 6: Обновить microservices/my_new_service/Cargo.toml
```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`:
```rust
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`:
```toml
[workspace]
members = [
"api_gateway",
"api",
"microservices/user_service",
"microservices/my_new_service",
"shared_proto"
]
```
### Шаг 9: Скомпилировать
```bash
cargo check
# или
cargo build
```
## 📚 Примеры
### Пример 1: Сервис продуктов
**shared_proto/proto/product.proto:**
```protobuf
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:**
```rust
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:**
```rust
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:
```rust
// Создать 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`)
- [ ] Добавлены тесты (опционально)
## 🐛 Отладка
### Проверить что сервис запускается
```bash
cargo run -p my_new_service
```
Должно вывести:
```
MyService gRPC server listening on 127.0.0.1:13002
```
### Проверить gRPC endpoint
```bash
# Установить grpcurl если не установлен
# https://github.com/fullstorydev/grpcurl
grpcurl -plaintext localhost:13002 list
```
### Логирование
```bash
RUST_LOG=debug cargo run -p my_new_service
```
## 📞 Помощь
Если возникли проблемы:
1. Проверьте что все proto файлы скомпилировались
2. Убедитесь что порт не занят другим процессом
3. Проверьте логи сервиса
4. Обратитесь к `ARCHITECTURE.md` для общей информации