diff --git a/cmd/example/app.go b/cmd/example/server.go similarity index 66% rename from cmd/example/app.go rename to cmd/example/server.go index 2583567..26192b1 100644 --- a/cmd/example/app.go +++ b/cmd/example/server.go @@ -6,25 +6,27 @@ import ( "google.golang.org/grpc" ) +// Entry point for the example application demonstrating how to use the gossiper package. func main() { - // Создаём менеджер серверов + // Create a new server manager to manage multiple servers. serverManager := gossiper.NewServerManager() - // Инициализация gRPC сервера + // Initialize the gRPC server. grpcInitRoute := func(server *grpc.Server) { - // Пример: добавить маршруты + // Example: Add gRPC routes here. } serverManager.AddServer(gossiper.NewGRPCServ("50051", grpc.NewServer(), grpcInitRoute)) - // Инициализация REST сервера + // Initialize the REST server. restInitRoute := func(router *gin.Engine) { + // Define a health check endpoint. router.GET("/health", func(c *gin.Context) { c.JSON(200, gin.H{"status": "ok"}) }) } serverManager.AddServer(gossiper.NewRESTServ("8080", gin.Default(), restInitRoute)) - // Запуск всех серверов + // Start all servers. serverManager.StartAll() defer serverManager.StopAll() } diff --git a/cmd/example/transport.go b/cmd/example/transport.go new file mode 100644 index 0000000..5ac505d --- /dev/null +++ b/cmd/example/transport.go @@ -0,0 +1,54 @@ +package main + +import ( + gossiper "github.com/pieceowater-dev/lotof.lib.gossiper" +) + +type SomeService struct { + transport gossiper.Transport + //client pb.SomeServiceClient // Add client as a property, generated from protobuf +} + +func NewSomeService() *SomeService { + factory := gossiper.NewTransportFactory() + grpcTransport := factory.CreateTransport( + gossiper.GRPC, + "localhost:50051", + ) + + // Create the client only once and store it as a property + //clientConstructor := pb.NewSomeServiceClient + //client, err := grpcTransport.CreateClient(clientConstructor) + //if err != nil { + // log.Fatalf("Error creating client: %v", err) + //} + + return &SomeService{ + transport: grpcTransport, + //client: client, + } +} + +//func (s *SomeService) Items() ([]any, error) { +// ctx := context.Background() +// +// // Send the request using the client stored in the SomeService instance +// response, err := s.transport.Send( +// ctx, +// s.client, +// "GetItems", +// &pb.GetItemsRequest{}, // Dynamic request for GetItems +// ) +// if err != nil { +// log.Printf("Error sending request: %v", err) +// return nil, err +// } +// +// // Assert the response to the correct type +// res, ok := response.(*pb.GetItemsResponse) +// if !ok { +// return nil, errors.New("invalid response type from gRPC transport") +// } +// +// return res, nil +//} diff --git a/gossiper.go b/gossiper.go index 2f25585..e28c287 100644 --- a/gossiper.go +++ b/gossiper.go @@ -2,13 +2,12 @@ package gossiper import ( "github.com/gin-gonic/gin" - "github.com/pieceowater-dev/lotof.lib.gossiper/internal/core/db" - "github.com/pieceowater-dev/lotof.lib.gossiper/internal/core/servers" - grpcServ "github.com/pieceowater-dev/lotof.lib.gossiper/internal/core/servers/grpc" - "github.com/pieceowater-dev/lotof.lib.gossiper/internal/core/servers/rabbitmq" - rmqServ "github.com/pieceowater-dev/lotof.lib.gossiper/internal/core/servers/rabbitmq" - "github.com/pieceowater-dev/lotof.lib.gossiper/internal/core/servers/rest" - restServ "github.com/pieceowater-dev/lotof.lib.gossiper/internal/core/servers/rest" + "github.com/pieceowater-dev/lotof.lib.gossiper/internal/db" + "github.com/pieceowater-dev/lotof.lib.gossiper/internal/servers" + grpcServ "github.com/pieceowater-dev/lotof.lib.gossiper/internal/servers/grpc" + rmqServ "github.com/pieceowater-dev/lotof.lib.gossiper/internal/servers/rabbitmq" + restServ "github.com/pieceowater-dev/lotof.lib.gossiper/internal/servers/rest" + "github.com/pieceowater-dev/lotof.lib.gossiper/internal/transport" "google.golang.org/grpc" ) @@ -42,10 +41,10 @@ type ServerManager = servers.ServerManager type GRPCServer = grpcServ.Server // RESTServer represents a REST server instance. -type RESTServer = rest.Server +type RESTServer = restServ.Server // RMQServer represents a RabbitMQ server instance. -type RMQServer = rabbitmq.Server +type RMQServer = rmqServ.Server // NewServerManager creates a new instance of the server manager. // The server manager is responsible for starting and stopping multiple server instances. @@ -76,3 +75,16 @@ func NewRESTServ(port string, router *gin.Engine, initRoute func(router *gin.Eng func NewRMQServ() *RMQServer { return rmqServ.New() } + +type Transport = transport.Transport +type TransportType = transport.Type + +const ( + GRPC TransportType = transport.GRPC +) + +type TransportFactory = transport.Factory + +func NewTransportFactory() *TransportFactory { + return transport.NewFactory() +} diff --git a/internal/core/db/database.go b/internal/db/database.go similarity index 98% rename from internal/core/db/database.go rename to internal/db/database.go index bb66e83..b754610 100644 --- a/internal/core/db/database.go +++ b/internal/db/database.go @@ -2,7 +2,7 @@ package db import ( "fmt" - postgresql "github.com/pieceowater-dev/lotof.lib.gossiper/internal/core/db/pg" + postgresql "github.com/pieceowater-dev/lotof.lib.gossiper/internal/db/pg" "gorm.io/gorm" ) diff --git a/internal/core/db/pg/pgsql.go b/internal/db/pg/pgsql.go similarity index 100% rename from internal/core/db/pg/pgsql.go rename to internal/db/pg/pgsql.go diff --git a/internal/core/servers/grpc/grpc.go b/internal/servers/grpc/grpc.go similarity index 100% rename from internal/core/servers/grpc/grpc.go rename to internal/servers/grpc/grpc.go diff --git a/internal/core/servers/rabbitmq/rabbitmq.go b/internal/servers/rabbitmq/rabbitmq.go similarity index 100% rename from internal/core/servers/rabbitmq/rabbitmq.go rename to internal/servers/rabbitmq/rabbitmq.go diff --git a/internal/core/servers/rest/rest.go b/internal/servers/rest/rest.go similarity index 100% rename from internal/core/servers/rest/rest.go rename to internal/servers/rest/rest.go diff --git a/internal/core/servers/server.go b/internal/servers/server.go similarity index 100% rename from internal/core/servers/server.go rename to internal/servers/server.go diff --git a/internal/transport/grpc.go b/internal/transport/grpc.go new file mode 100644 index 0000000..86cc0ce --- /dev/null +++ b/internal/transport/grpc.go @@ -0,0 +1,66 @@ +package transport + +import ( + "context" + "errors" + "google.golang.org/grpc" + "google.golang.org/grpc/credentials/insecure" + "reflect" +) + +// GRPCTransport handles both client and server-side transport +type GRPCTransport struct { + address string + server *grpc.Server +} + +func NewGRPCTransport(address string) *GRPCTransport { + return &GRPCTransport{address: address} +} + +// CreateClient dynamically creates a gRPC client using the passed constructor. +func (g *GRPCTransport) CreateClient(clientConstructor any) (any, error) { + // Dial the gRPC connection. + conn, err := grpc.Dial(g.address, grpc.WithTransportCredentials(insecure.NewCredentials())) + if err != nil { + return nil, errors.New("failed to connect to gRPC server: " + err.Error()) + } + + // Use reflection to call the constructor function dynamically + constructorValue := reflect.ValueOf(clientConstructor) + if constructorValue.Kind() != reflect.Func { + return nil, errors.New("clientConstructor must be a function") + } + + // Call the constructor to create the client (pass the connection as argument) + clientValues := constructorValue.Call([]reflect.Value{reflect.ValueOf(conn)}) + + // Ensure that the client creation was successful and return the client + if len(clientValues) > 0 { + return clientValues[0].Interface(), nil + } + return nil, errors.New("failed to create client") +} + +// Send sends a dynamic gRPC request based on method name and request type +func (g *GRPCTransport) Send(ctx context.Context, client any, serviceMethod string, request any) (any, error) { + // Use reflection to get the method from the client dynamically + clientValue := reflect.ValueOf(client) + method := clientValue.MethodByName(serviceMethod) + if !method.IsValid() { + return nil, errors.New("invalid service method: " + serviceMethod) + } + + // Ensure the request is passed as a reflect.Value + reqValue := reflect.ValueOf(request) + if reqValue.IsValid() { + // Call the method dynamically, passing the context and the request + returnValues := method.Call([]reflect.Value{reflect.ValueOf(ctx), reqValue}) + if len(returnValues) > 1 && returnValues[1].Interface() != nil { + return nil, returnValues[1].Interface().(error) + } + // Return the response from the method call + return returnValues[0].Interface(), nil + } + return nil, errors.New("invalid request type for method") +} diff --git a/internal/transport/transport.go b/internal/transport/transport.go new file mode 100644 index 0000000..f851d35 --- /dev/null +++ b/internal/transport/transport.go @@ -0,0 +1,29 @@ +package transport + +import "context" + +type Transport interface { + CreateClient(clientConstructor any) (any, error) + Send(ctx context.Context, client any, serviceMethod string, request any) (any, error) +} + +type Type string + +const ( + GRPC Type = "grpc" +) + +type Factory struct{} + +func NewFactory() *Factory { + return &Factory{} +} + +func (f *Factory) CreateTransport(transportType Type, address string) Transport { + switch transportType { + case GRPC: + return NewGRPCTransport(address) + default: + return nil + } +}