소스 정보
- 저장소
- tools-only/X-Skills
- 최근 소스 활동
- 2026년 2월 9일 04:08
- 감지된 SKILL.md 언어
- 영어
- 스타
- 7
- 포크
- 1
설치 방법
기본적으로 소스를 먼저 확인하는 Prompt가 선택됩니다. 직접 명령으로 전환하거나 로컬 사본을 다운로드할 수도 있습니다.
소스 파일 검토
설치 여부를 결정하기 전에 SKILL.md와 SkillsMP에 표시된 보조 파일을 읽어 보세요.
메뉴
기본적으로 소스를 먼저 확인하는 Prompt가 선택됩니다. 직접 명령으로 전환하거나 로컬 사본을 다운로드할 수도 있습니다.
설치 여부를 결정하기 전에 SKILL.md와 SkillsMP에 표시된 보조 파일을 읽어 보세요.
Codex 또는 Claude로 설치 이 Prompt를 복사해 Codex, Claude 또는 다른 어시스턴트에 붙여 넣으면 Skill 페이지를 검토하고 설치를 진행할 수 있습니다.
직접 명령은 검토 Prompt를 거치지 않습니다. 실행하기 전에 소스를 확인하세요.
npx skills add https://github.com/tools-only/X-Skills --skill generate-grpc-service명령은 한 줄로 유지됩니다. 복사하기 전에 가로로 스크롤해 전체 내용을 확인하세요.
로컬 사본을 원하시나요? SkillsMP에서 현재 제공할 수 있는 파일을 다운로드하세요.
SOC 직업 분류 기준
SKILL.md 표시 중
| name | generate-grpc-service |
| description | Generate production-ready gRPC services with Protocol Buffers |
| shortcut | grpc |
Automatically generate high-performance gRPC services with Protocol Buffer definitions, streaming support, load balancing, and comprehensive service implementations for multiple programming languages.
Use /generate-grpc-service when you need to:
DON'T use this when:
This command implements gRPC with Protocol Buffers v3 as the primary approach because:
Alternative considered: Apache Thrift
Alternative considered: GraphQL with subscriptions
Before running this command:
Create comprehensive .proto files with service definitions and message types.
Compile Protocol Buffers to target language code with gRPC plugins.
Build server-side implementations for all RPC methods.
Implement cross-cutting concerns like auth, logging, and error handling.
Set up TLS, connection pooling, and load balancing.
The command generates:
proto/service.proto - Protocol Buffer definitionsserver/ - Server implementation with all RPC methodsclient/ - Client library with connection managementinterceptors/ - Authentication, logging, metrics interceptorsconfig/ - TLS certificates and configurationdocs/api.md - Service documentation// proto/ecommerce.proto
syntax = "proto3";
package ecommerce.v1;
import "google/protobuf/timestamp.proto";
import "google/protobuf/empty.proto";
// Service definition with all RPC patterns
service ProductService {
// Unary RPC
rpc GetProduct(GetProductRequest) returns (Product);
// Server streaming
rpc ListProducts(ListProductsRequest) returns (stream Product);
// Client streaming
rpc ImportProducts(stream Product) returns (ImportSummary);
// Bidirectional streaming
rpc WatchInventory(stream InventoryUpdate) returns (stream InventoryChange);
// Batch operations
rpc BatchGetProducts(BatchGetProductsRequest) returns (BatchGetProductsResponse);
}
// Message definitions
message Product {
string id = 1;
string name = 2;
string description = 3;
double price = 4;
int32 inventory = 5;
repeated string categories = 6;
map<string, string> metadata = 7;
google.protobuf.Timestamp created_at = 8;
google.protobuf.Timestamp updated_at = 9;
enum Status {
STATUS_UNSPECIFIED = 0;
STATUS_ACTIVE = 1;
STATUS_DISCONTINUED = 2;
STATUS_OUT_OF_STOCK = 3;
}
Status status = 10;
}
message GetProductRequest {
string product_id = 1;
repeated string fields = 2; // Field mask for partial responses
}
message ListProductsRequest {
string category = 1;
int32 page_size = 2;
string page_token = 3;
string order_by = 4;
message Filter {
double min_price = 1;
double max_price = 2;
repeated string tags = 3;
}
Filter filter = 5;
}
message ImportSummary {
int32 total_received = 1;
int32 successful = 2;
int32 failed = 3;
repeated ImportError errors = 4;
}
message ImportError {
int32 index = 1;
string product_id = 2;
string error = 3;
}
message InventoryUpdate {
string product_id = 1;
int32 quantity_change = 2;
string warehouse_id = 3;
}
message InventoryChange {
string product_id = 1;
int32 old_quantity = 2;
int32 new_quantity = 3;
google.protobuf.Timestamp timestamp = 4;
string triggered_by = 5;
}
message BatchGetProductsRequest {
repeated string product_ids = 1;
repeated string fields = 2;
}
message BatchGetProductsResponse {
repeated Product products = 1;
repeated string not_found = 2;
}
// server/main.go - Go server implementation
package main
import (
"context"
"crypto/tls"
"fmt"
"io"
"log"
"net"
"sync"
"time"
pb "github.com/company/ecommerce/proto"
"github.com/golang/protobuf/ptypes/empty"
"google.golang.org/grpc"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/credentials"
"google.golang.org/grpc/keepalive"
"google.golang.org/grpc/metadata"
"google.golang.org/grpc/status"
"google.golang.org/protobuf/types/known/timestamppb"
)
type productServer struct {
pb.UnimplementedProductServiceServer
mu sync.RWMutex
products map[string]*pb.Product
watchers map[string]chan *pb.InventoryChange
}
// Unary RPC implementation
func (s *productServer) GetProduct(
ctx context.Context,
req *pb.GetProductRequest,
) (*pb.Product, error) {
// Extract metadata for tracing
if md, ok := metadata.FromIncomingContext(ctx); ok {
if traceID := md.Get("trace-id"); len(traceID) > 0 {
log.Printf("GetProduct request - trace: %s", traceID[0])
}
}
s.mu.RLock()
product, exists := s.products[req.ProductId]
s.mu.RUnlock()
if !exists {
return , status.Errorf(
codes.NotFound,
,
req.ProductId,
)
}
(req.Fields) > {
applyFieldMask(product, req.Fields),
}
product,
}
ListProducts(
req *pb.ListProductsRequest,
stream pb.ProductService_ListProductsServer,
) {
s.mu.RLock()
s.mu.RUnlock()
count :=
_, product := s.products {
!matchesFilter(product, req) {
}
err := stream.Send(product); err != {
status.Errorf(
codes.Internal,
,
err,
)
}
count++
req.PageSize > && count >= (req.PageSize) {
}
time.Sleep( * time.Millisecond)
}
}
ImportProducts(
stream pb.ProductService_ImportProductsServer,
) {
summary pb.ImportSummary
errors []*pb.ImportError
index :=
{
product, err := stream.Recv()
err == io.EOF {
summary.Errors = errors
stream.SendAndClose(&summary)
}
err != {
status.Errorf(
codes.Internal,
,
err,
)
}
summary.TotalReceived++
err := validateProduct(product); err != {
summary.Failed++
errors = (errors, &pb.ImportError{
Index: (index),
ProductId: product.Id,
Error: err.Error(),
})
} {
s.mu.Lock()
s.products[product.Id] = product
s.mu.Unlock()
summary.Successful++
}
index++
}
}
WatchInventory(
stream pb.ProductService_WatchInventoryServer,
) {
changeChan := ( *pb.InventoryChange, )
clientID := generateClientID()
s.mu.Lock()
s.watchers[clientID] = changeChan
s.mu.Unlock()
{
s.mu.Lock()
(s.watchers, clientID)
s.mu.Unlock()
(changeChan)
}()
errChan := ( , )
{
{
update, err := stream.Recv()
err == io.EOF {
errChan <-
}
err != {
errChan <- err
}
err := s.processInventoryUpdate(update); err != {
log.Printf(, err)
}
change := &pb.InventoryChange{
ProductId: update.ProductId,
NewQuantity: s.getInventory(update.ProductId),
Timestamp: timestamppb.Now(),
TriggeredBy: clientID,
}
s.broadcastChange(change)
}
}()
{
change := changeChan {
err := stream.Send(change); err != {
errChan <- err
}
}
}()
<-errChan
}
({}, ) {
md, ok := metadata.FromIncomingContext(ctx)
!ok {
, status.Error(codes.Unauthenticated, )
}
tokens := md.Get()
(tokens) == {
, status.Error(codes.Unauthenticated, )
}
!isValidToken(tokens[]) {
, status.Error(codes.Unauthenticated, )
}
handler(ctx, req)
}
{
cert, err := tls.LoadX509KeyPair(, )
err != {
log.Fatalf(, err)
}
config := &tls.Config{
Certificates: []tls.Certificate{cert},
ClientAuth: tls.RequireAndVerifyClientCert,
}
creds := credentials.NewTLS(config)
opts := []grpc.ServerOption{
grpc.Creds(creds),
grpc.UnaryInterceptor(authInterceptor),
grpc.KeepaliveParams(keepalive.ServerParameters{
MaxConnectionIdle: * time.Minute,
Time: * time.Minute,
Timeout: * time.Second,
}),
grpc.KeepaliveEnforcementPolicy(keepalive.EnforcementPolicy{
MinTime: * time.Second,
PermitWithoutStream: ,
}),
grpc.MaxConcurrentStreams(),
}
server := grpc.NewServer(opts...)
pb.RegisterProductServiceServer(server, &productServer{
products: ([]*pb.Product),
watchers: ([] *pb.InventoryChange),
})
lis, err := net.Listen(, )
err != {
log.Fatalf(, err)
}
log.Println()
err := server.Serve(lis); err != {
log.Fatalf(, err)
}
}
# client/product_client.py
import grpc
import asyncio
import logging
from typing import List, Optional, AsyncIterator
from concurrent import futures
from grpc import aio
import backoff
from proto import ecommerce_pb2 as pb
from proto import ecommerce_pb2_grpc as pb_grpc
logger = logging.getLogger(__name__)
class ProductClient:
"""Enhanced gRPC client with retry, load balancing, and connection pooling."""
def __init__(
self,
servers: List[str],
api_key: Optional[str] = None,
use_tls: bool = True,
pool_size: int = 10
):
self.servers = servers
self.api_key = api_key
self.use_tls = use_tls
self.pool_size = pool_size
self.channels = []
self.stubs = []
self._round_robin_counter = 0
self._setup_channels()
def _setup_channels(self):
server .servers:
_ (.pool_size // (.servers)):
.use_tls:
(, ) f:
client_cert = f.read()
(, ) f:
client_key = f.read()
(, ) f:
ca_cert = f.read()
credentials = grpc.ssl_channel_credentials(
root_certificates=ca_cert,
private_key=client_key,
certificate_chain=client_cert
)
channel = aio.secure_channel(
server,
credentials,
options=[
(, ),
(, ),
(, ),
(, ),
]
)
:
channel = aio.insecure_channel(
server,
options=[
(, ),
(, ),
]
)
.channels.append(channel)
.stubs.append(pb_grpc.ProductServiceStub(channel))
() -> pb_grpc.ProductServiceStub:
stub = .stubs[._round_robin_counter]
._round_robin_counter = (._round_robin_counter + ) % (.stubs)
stub
() -> []:
metadata = []
.api_key:
metadata.append((, ))
metadata.append((, ._generate_trace_id()))
metadata
() -> pb.Product:
request = pb.GetProductRequest(
product_id=product_id,
fields=fields []
)
:
response = ._get_stub().GetProduct(
request,
metadata=._get_metadata(),
timeout=
)
response
grpc.RpcError e:
logger.error()
() -> AsyncIterator[pb.Product]:
request = pb.ListProductsRequest(
category=category ,
page_size=page_size
)
min_price max_price :
request..CopyFrom(pb.ListProductsRequest.Filter(
min_price=min_price ,
max_price=max_price ()
))
:
stream = ._get_stub().ListProducts(
request,
metadata=._get_metadata(),
timeout=
)
product stream:
product
grpc.RpcError e:
logger.error()
() -> pb.ImportSummary:
():
product products:
product
asyncio.sleep()
:
response = ._get_stub().ImportProducts(
generate_products(),
metadata=._get_metadata(),
timeout=
)
response.failed > :
logger.warning(
)
response
grpc.RpcError e:
logger.error()
() -> AsyncIterator[pb.InventoryChange]:
:
stream = ._get_stub().WatchInventory(
metadata=._get_metadata()
)
send_task = asyncio.create_task(._send_updates(stream, updates))
:
change stream:
change
:
send_task.cancel()
grpc.RpcError e:
logger.error()
():
:
update updates:
stream.write(update)
stream.done_writing()
asyncio.CancelledError:
():
close_tasks = [channel.close() channel .channels]
asyncio.gather(*close_tasks)
() -> :
uuid
(uuid.uuid4())
():
client = ProductClient(
servers=[
,
,
],
api_key=,
use_tls=
)
:
product = client.get_product()
()
product client.list_products(
category=,
min_price=,
max_price=
):
()
products_to_import = [
pb.Product(=, name=, price=)
i ()
]
summary = client.import_products(products_to_import)
()
():
i ():
pb.InventoryUpdate(
product_id=,
quantity_change=,
warehouse_id=
)
asyncio.sleep()
change client.watch_inventory(generate_updates()):
()
:
client.close()
__name__ == :
asyncio.run(main())
// server/index.js
const grpc = require('@grpc/grpc-js');
const protoLoader = require('@grpc/proto-loader');
const path = require('path');
// Load proto file
const PROTO_PATH = path.join(__dirname, '../proto/ecommerce.proto');
const packageDefinition = protoLoader.loadSync(PROTO_PATH, {
keepCase: true,
longs: String,
enums: String,
defaults: true,
oneofs: true
});
const protoDescriptor = grpc.loadPackageDefinition(packageDefinition);
const ecommerce = protoDescriptor.ecommerce.v1;
// Health check implementation
const health = require('grpc-health-check');
const healthImpl = new health.Implementation({
'': 'SERVING',
'ecommerce.v1.ProductService': 'SERVING'
});
// Service implementation
class ProductService {
constructor() {
. = ();
. = ();
}
() {
{ product_id } = call.;
product = ..(product_id);
(!product) {
({
: grpc..,
:
});
;
}
(, product);
}
() {
{ category, page_size } = call.;
count = ;
( [id, product] .) {
(category && product..(category) === -) {
;
}
call.(product);
count++;
(page_size > && count >= page_size) {
;
}
( (resolve, ));
}
call.();
}
}
() {
server = grpc.({
: ,
: * *
});
server.(
ecommerce..,
()
);
server.(health., healthImpl);
server.(
,
grpc..(),
{
(err) {
.(, err);
;
}
.();
server.();
}
);
}
();
| Error | Cause | Solution |
|---|---|---|
| "Failed to compile proto" | Invalid Protocol Buffer syntax | Validate with protoc --lint |
| "Connection refused" | Server not running or wrong port | Check server status and port |
| "Deadline exceeded" | Request timeout | Increase timeout or optimize operation |
| "Resource exhausted" | Rate limiting or quota exceeded | Implement backoff and retry |
| "Unavailable" | Server temporarily down | Implement circuit breaker pattern |
Server Options
MaxConcurrentStreams: Limit concurrent streams per connectionMaxReceiveMessageSize: Maximum message size (default 4MB)KeepaliveParams: Connection health monitoringConnectionTimeout: Maximum idle time before closingClient Options
LoadBalancingPolicy: round_robin, pick_first, grpclbWaitForReady: Block until server availableRetry: Automatic retry configurationInterceptors: Add cross-cutting concernsDO:
DON'T:
/rest-api-generator - Generate REST APIs/graphql-server-builder - Build GraphQL servers/api-gateway-builder - Create API gateways/webhook-handler-creator - Handle webhooks/websocket-server-builder - WebSocket servers