Services
This guide covers how to define, register, and consume domain services in the aegis mesh.
Defining Services
Services are defined using Protocol Buffers. Aegis hosts shared proto files in proto/:
aegis/
└── proto/
└── identity/
├── identity.proto
├── identity.pb.go
└── identity_grpc.pb.go
Example Proto
syntax = "proto3";
package aegis.identity.v1;
option go_package = "github.com/zoobz-io/aegis/proto/identity";
service IdentityService {
rpc ValidateSession(ValidateSessionRequest) returns (ValidateSessionResponse);
rpc GetUser(GetUserRequest) returns (GetUserResponse);
}
message ValidateSessionRequest {
string token = 1;
}
message ValidateSessionResponse {
bool valid = 1;
string user_id = 2;
int64 expires_at = 3;
}
Generating Code
protoc --go_out=. --go_opt=paths=source_relative \
--go-grpc_out=. --go-grpc_opt=paths=source_relative \
proto/identity/identity.proto
Or use the Makefile:
make proto
Registering Services
Implementing the Server
package main
import (
"context"
"github.com/zoobz-io/aegis"
identity "github.com/zoobz-io/aegis/proto/identity"
)
type identityServer struct {
identity.UnimplementedIdentityServiceServer
// Add your dependencies
sessions SessionStore
users UserStore
}
func (s *identityServer) ValidateSession(ctx context.Context, req *identity.ValidateSessionRequest) (*identity.ValidateSessionResponse, error) {
session, err := s.sessions.Get(ctx, req.Token)
if err != nil {
return &identity.ValidateSessionResponse{Valid: false}, nil
}
return &identity.ValidateSessionResponse{
Valid: true,
UserId: session.UserID,
ExpiresAt: session.ExpiresAt.Unix(),
}, nil
}
Registering with Node
Use WithServiceRegistration to register your service:
node, err := aegis.NewNodeBuilder().
WithID("morpheus-1").
WithName("Morpheus").
WithAddress("localhost:8443").
WithServices(aegis.ServiceInfo{Name: "identity", Version: "v1"}).
WithServiceRegistration(func(s *grpc.Server) {
identity.RegisterIdentityServiceServer(s, &identityServer{
sessions: mySessionStore,
users: myUserStore,
})
}).
WithCertDir("./certs").
Build()
The WithServices declaration advertises the service in topology. The WithServiceRegistration callback registers the gRPC handler.
Multiple Services
Register multiple services by chaining:
WithServices(
aegis.ServiceInfo{Name: "identity", Version: "v1"},
aegis.ServiceInfo{Name: "audit", Version: "v1"},
).
WithServiceRegistration(func(s *grpc.Server) {
identity.RegisterIdentityServiceServer(s, &identityServer{})
audit.RegisterAuditServiceServer(s, &auditServer{})
})
Consuming Services
Creating a Client
pool := aegis.NewServiceClientPool(node)
defer pool.Close()
client := aegis.NewServiceClient(pool, "identity", "v1", identity.NewIdentityServiceClient)
Making Calls
// Get a client (round-robin across providers)
identityClient, err := client.Get(ctx)
if err != nil {
return err // No providers available
}
resp, err := identityClient.ValidateSession(ctx, &identity.ValidateSessionRequest{
Token: userToken,
})
if err != nil {
return err // RPC failed
}
if resp.Valid {
log.Printf("User: %s", resp.UserId)
}
Error Handling
identityClient, err := client.Get(ctx)
if errors.Is(err, aegis.ErrNoProviders) {
// No nodes provide this service
return fallbackBehavior()
}
if errors.Is(err, aegis.ErrNoTLSConfig) {
// Node not configured with TLS
return configurationError()
}
Caller Authorization
Services can authorize callers using mTLS identity:
func (s *identityServer) ValidateSession(ctx context.Context, req *identity.ValidateSessionRequest) (*identity.ValidateSessionResponse, error) {
caller, err := aegis.CallerFromContext(ctx)
if err != nil {
return nil, status.Error(codes.Unauthenticated, "no caller identity")
}
// Check if caller is allowed
if !s.allowedNodes[caller.NodeID] {
return nil, status.Error(codes.PermissionDenied, "node not authorized")
}
// Proceed with request
// ...
}
Allowlist Pattern
type identityServer struct {
identity.UnimplementedIdentityServiceServer
allowedNodes map[string]bool
}
func NewIdentityServer(allowedNodes []string) *identityServer {
allowed := make(map[string]bool)
for _, id := range allowedNodes {
allowed[id] = true
}
return &identityServer{allowedNodes: allowed}
}
Node Type Authorization
// In the calling node
WithType(aegis.NodeType("api-gateway"))
// In the service
caller, _ := aegis.CallerFromContext(ctx)
cert := caller.Certificate
// Extract node type from certificate or lookup from topology
Service Versioning
Multiple Versions
Run multiple versions simultaneously:
WithServices(
aegis.ServiceInfo{Name: "identity", Version: "v1"},
aegis.ServiceInfo{Name: "identity", Version: "v2"},
)
Version Selection
Clients explicitly request a version:
// v1 client
clientV1 := aegis.NewServiceClient(pool, "identity", "v1", identityv1.NewIdentityServiceClient)
// v2 client
clientV2 := aegis.NewServiceClient(pool, "identity", "v2", identityv2.NewIdentityServiceClient)
Migration Strategy
- Deploy providers with both v1 and v2
- Update consumers to use v2
- Remove v1 from providers
- Remove v1 proto definitions
Next Steps
- Certificates Guide — Certificate management
- API Reference — Function signatures