skills/microservice/grpc/service-definition/SKILL.md
Use when designing proto files or implementing gRPC service classes with MediatR.
npx skillsauth add faysilalshareef/dotnet-ai-kit service-definitionInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
.proto files)*Base classgoogle.protobuf.StringValue for nullable, google.protobuf.Timestamp for DateTimePageRequest/PageResponse messagessyntax = "proto3";
package {company}.{domain};
import "google/protobuf/wrappers.proto";
import "google/protobuf/timestamp.proto";
option csharp_namespace = "{Company}.{Domain}.Grpc";
// Command service
service OrderCommands {
rpc CreateOrder (CreateOrderRequest) returns (CreateOrderResponse);
rpc UpdateOrder (UpdateOrderRequest) returns (UpdateOrderResponse);
rpc CompleteOrder (CompleteOrderRequest) returns (CompleteOrderResponse);
}
// Query service
service OrderQueries {
rpc GetOrder (GetOrderRequest) returns (OrderResponse);
rpc GetOrders (GetOrdersRequest) returns (GetOrdersResponse);
}
// Messages
message CreateOrderRequest {
string customer_name = 1;
// C-Q3 fix: use minor-unit integers (cents) for money — `double` loses
// precision and is the canonical money anti-pattern. See the anti-pattern
// table below for the recommended choices (int64 cents, string-decimal,
// or google.type.Money / DecimalValue wrapper).
int64 total_cents = 2;
repeated OrderItemMessage items = 3;
}
message CreateOrderResponse {
string order_id = 1;
int32 sequence = 2;
}
message GetOrdersRequest {
int32 page = 1;
int32 page_size = 2;
google.protobuf.StringValue search = 3; // nullable
google.protobuf.StringValue status = 4; // nullable
}
message GetOrdersResponse {
repeated OrderSummary items = 1;
int32 total_count = 2;
int32 page = 3;
int32 page_size = 4;
}
message OrderSummary {
string id = 1;
string customer_name = 2;
int64 total_cents = 3; // C-Q3: minor-unit integer (cents)
string status = 4;
}
message OrderItemMessage {
string product_id = 1;
int32 quantity = 2;
int64 unit_price_cents = 3; // C-Q3: minor-unit integer (cents)
}
namespace {Company}.{Domain}.Grpc.Services;
public sealed class OrderCommandsService(IMediator mediator)
: OrderCommands.OrderCommandsBase
{
public override async Task<CreateOrderResponse> CreateOrder(
CreateOrderRequest request, ServerCallContext context)
{
var command = request.ToCommand();
// C-Q1 fix: forward the RPC's cancellation token so MediatR
// honors client-cancelled / deadline-exceeded RPCs.
var output = await mediator.Send(command, context.CancellationToken);
return output.ToCreateResponse();
}
public override async Task<UpdateOrderResponse> UpdateOrder(
UpdateOrderRequest request, ServerCallContext context)
{
var command = request.ToCommand();
// C-Q1 fix: forward the RPC's cancellation token (see CreateOrder).
var output = await mediator.Send(command, context.CancellationToken);
return output.ToUpdateResponse();
}
}
namespace {Company}.{Domain}.Grpc.Extensions;
public static class OrderMappingExtensions
{
// Request → Command
public static CreateOrderCommand ToCommand(this CreateOrderRequest r) =>
new(r.CustomerName, (decimal)r.Total,
r.Items.Select(i => new OrderItemInput(
Guid.Parse(i.ProductId), i.Quantity, (decimal)i.UnitPrice
)).ToList());
// Output → Response
public static CreateOrderResponse ToCreateResponse(this OrderOutput o) =>
new() { OrderId = o.Id.ToString(), Sequence = o.Sequence };
// Nullable proto → C# nullable
public static string? ToNullable(this StringValue? value) =>
value?.Value;
// DateTime → Timestamp
public static Timestamp ToTimestamp(this DateTime dt) =>
Timestamp.FromDateTime(dt.ToUniversalTime());
}
// Server-side registration
builder.Services.AddGrpc(options =>
{
options.Interceptors.Add<ApplicationExceptionInterceptor>();
options.Interceptors.Add<ThreadCultureInterceptor>();
});
var app = builder.Build();
app.MapGrpcService<OrderCommandsService>();
app.MapGrpcService<OrderQueriesService>();
| Anti-Pattern | Correct Approach |
|---|---|
| Using string for nullable fields | Use google.protobuf.StringValue |
| Business logic in gRPC service | Delegate to MediatR handlers |
| Missing mapping extensions | Always have explicit ToCommand/ToResponse |
| Using float or double for money | C-Q3: use minor-unit integers (int64 cents), string for fixed-point decimals, or a DecimalValue/google.type.Money wrapper. Never double. |
| Hardcoded strings in service | Use resource strings for error messages |
# Find .proto files
find . -name "*.proto" -type f
# Find gRPC service implementations
grep -r "CommandsBase\|QueriesBase" --include="*.cs" src/
# Find mapping extensions
grep -r "ToCommand\|ToResponse" --include="*.cs" src/Grpc/
# Find MapGrpcService
grep -r "MapGrpcService" --include="*.cs" src/
{domain}-commands.proto, {domain}-queries.protoGrpc/Extensions/ directoryMapGrpcService<T>() in Program.cs.csproj with proto file reference (GrpcServices="Server" or "Both")data-ai
Use when about to claim work is complete, fixed, passing, or ready — before committing, creating PRs, or moving to the next task. Requires running verification commands and confirming output before making any success claims.
development
Use when encountering any bug, test failure, build error, or unexpected behavior — before proposing fixes or making changes.
development
Use when checkpointing, wrapping up, or handing off an AI-assisted development session.
development
Use when following the Specification-Driven Development lifecycle from plan through ship.