Although being an opinionated framework, it is still highly extensible and allows you to plug in your own implementations for various components. The framework is heavily inspired by go-micro. The framework is split into modules and does not bloat the codebase so that you import only the minimal required components for your microservice.
It comes with a project generator to get you started very quickly. It is a Yeoman Generator and you can find it here
The Generator uses GRPC + Protobuf + NATS + Kubernetes + Skaffold + Echo + Nginx and supports
- Automatic containerization of services and deployment using kubernetes
- Auto reload on code change
- Clearly defined interfaces for services using protobuf
- Defines the broker interface.
- NATS is supported and used by default
- Broker uses protobuf message encoding
import (
"log"
"github.com/adityak368/ego/broker"
"github.com/adityak368/ego/broker/nats"
// Replace this with your own protobuf message
"email/proto/email"
)
bkr := nats.New()
bkr.Init(broker.Options{
Name: "Nats",
Address: "localhost:4222",
})
err := bkr.Connect()
if err != nil {
log.Fatal(err)
}
// SendEmailRequest is a protobuf message
func OnEmail(msg *email.SendEmailRequest) error {
// Handle new email request
return nil
}
func OnUserCreatedRaw(msg []byte) error {
// Handle new user creation
return nil
}
emailsubscription, err := bkr.Subscribe("email.SendEmail", OnEmail)
if err != nil {
log.Fatal(err)
}
usersubscription, err := bkr.SubscribeRaw("user.UserCreated", OnUserCreatedRaw)
if err != nil {
log.Fatal(err)
}
// Publish the protobuf message to the broker
bkr.Publish("email.SendEmail", &email.SendEmailRequest{
Subject: "[email protected]",
})
// Publish raw message to the broker
bkr.PublishRaw("user.UserCreated", []byte("Data"))
syntax = "proto3";
package email;
message SendEmailRequest {
string To = 1;
string Subject = 2;
string Body = 3;
}
- Defines the Registry interface. Default implementation is mdns. Useful only during local development as most cloud providers block mdns.
import (
"log"
"github.com/adityak368/ego/registry"
"github.com/adityak368/ego/registry/mdns"
)
service := "MyApp"
serviceName := "MyAwesomeMicroService"
domain := "local"
reg := mdns.New(service, domain)
reg.Init(registry.Options{})
reg.Register(registry.Entry{
Name: serviceName, // Name of the service to register
Address: "localhost:1212", // Address of the service to register
Version: "1.0.0", // Version of the service to register
})
err := reg.Watch()
if err != nil {
return err
}
defer reg.Deregister(serviceName)
defer reg.CancelWatch()
- Defines the Server interface which serves RPC requests from other microservices
- GRPC is supported and used by default
import (
"log"
// replace with your own service protobuf definition
"myawesomeapp/proto/myawesomeapp"
"github.com/adityak368/ego/registry/mdns"
"github.com/adityak368/ego/server"
grpcServer "github.com/adityak368/ego/server/grpc"
)
func CreateUser(ctx context.Context, req *myawesomeapp.CreateUserRequest) (*myawesomeapp.CreateUserResponse, error) {
return &myawesomeapp.CreateUserResponse{
Success: true,
}, nil
}
// Create and start a new grpc server
srv := grpcServer.New()
srv.Init(server.Options{
Name: "MyAwesomeApp",
Address: "localhost:4003",
Registry: mdns.New("ego", "local"), // Set registry to mdns for automatic service discovery
Version: "1.0.0",
})
grpcHandle := srv.Handle().(*grpc.Server)
// Register the protobuf service with the grpc server
myawesomeapp.RegisterMyAwesomeAppService(grpcHandle, &myawesomeapp.MyAwesomeAppService{
CreateUser: CreateUser,
})
syntax = "proto3";
package myawesomeapp;
service MyAwesomeApp {
rpc CreateUser(CreateUserRequest) returns (CreateUserResponse) {}
}
message CreateUserRequest {
string Name = 1;
string Details = 2;
}
message CreateUserResponse {
bool success = 1;
}
message UserCreated {
string Name = 1;
string Details = 2;
}
- Defines the Client interface which makes a RPC
- GRPC is supported and used by default
import (
"log"
// replace with your own service protobuf definition
"anotherservice/proto/anotherservice"
"github.com/adityak368/ego/client"
grpcClient "github.com/adityak368/ego/client/grpc"
"github.com/adityak368/mdnsresolver"
"google.golang.org/grpc"
)
anotherServiceClient := grpcClient.New(
grpc.WithInsecure(),
grpc.WithResolvers(mdnsresolver.NewBuilder()),
grpc.WithBalancerName("round_robin"),
)
// Initialize the client
anotherServiceClient.Init(client.Options{
Name: "AnotherServiceClient", // Initialize the client by giving it a name
Target: "mdns://ego/AnotherService.local", // Set service discovery mechanism. MDNS is used here
})
// Connect the client to server
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
err := anotherServiceClient.Connect(ctx)
if err != nil {
log.Fatal(err)
}
conn := anotherServiceClient.Handle().(*grpc.ClientConn)
anotherServiceClient = anotherservice.NewAnotherServiceClient(conn)
// Do RPC Calls
// anotherServiceClient.SendEmail(...)
syntax = "proto3";
package anotherservice;
service AnotherService {
rpc SendEmail(SendEmailRequest) returns (SendEmailResponse) {}
}
message SendEmailRequest {
string To = 1;
string Subject = 2;
string Body = 3;
}
message SendEmailResponse {
bool success = 1;
}
- Defines the Database and Model interface for connecting to the database
- Contains implementations for MongoDB and Redis
import (
"log"
"github.com/adityak368/ego/db"
"github.com/adityak368/ego/db/mongodb"
)
// MongoDB exports the mongodb handle
MongoDB := mongodb.New()
MongoDB.Init(db.Options{
Name: "Mongodb",
Address: "localhost:27017",
Database: "MyDatabase",
})
err := MongoDB.Connect()
if err != nil {
log.Fatal(err)
}
userModel := UserModel()
userModel.CreateIndexes(MongoDB)
userModel.PrintIndexes(MongoDB)
defer MongoDB.Disconnect()
The User Model
import (
"context"
"time"
"log"
"github.com/adityak368/ego/db"
"github.com/adityak368/ego/db/mongodb"
"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/bson/primitive"
"go.mongodb.org/mongo-driver/mongo"
"go.mongodb.org/mongo-driver/mongo/options"
)
// User defines the user model
type User struct {
ID primitive.ObjectID `bson:"_id" json:"id" validate:"required"`
Name string `bson:"name,omitempty" json:"name" validate:"required"`
Details string `bson:"details,omitempty" json:"details" validate:"required"`
}
// CreateIndexes creates the indexes for a model
func (u *User) CreateIndexes(db db.Database) error {
c := db.Handle().(*mongo.Database).Collection(u.String())
opts := options.CreateIndexes().SetMaxTime(10 * time.Second)
keys := bson.D{{"name", 1}}
index := mongo.IndexModel{}
index.Keys = keys
index.Options = &options.IndexOptions{Unique: &[]bool{true}[0]}
c.Indexes().CreateOne(context.Background(), index, opts)
return nil
}
// PrintIndexes prints all the indexes for the model
func (u *User) PrintIndexes(db db.Database) {
switch db.(type) {
case *mongodb.DB:
d := db.(*mongodb.DB)
d.PrintIndexes(u.String())
default:
log.Println("Could not load indexes")
}
}
// String returns the string representation of the model
func (u *User) String() string {
return "User"
}
// UserModel returns user as a db model
func UserModel() db.Model {
return &User{}
}