diff --git a/README.md b/README.md index 99f1089..4bc4467 100644 --- a/README.md +++ b/README.md @@ -1 +1,91 @@ -# todo \ No newline at end of file + +# HTTP Gateway with gRPC Backend + + +This project provides an HTTP gateway with a gRPC backend implementation. It allows you to interact with the backend services using HTTP requests. + +## Getting Started + + +These instructions will help you get the project up and running on your local machine. + + +### Prerequisites + + +- Docker: Make sure you have Docker installed on your system. You can download Docker from the official website: [https://www.docker.com](https://www.docker.com) + + + +### Installation + + + +1. Clone the repository: + + + +```shell + +git clone https://github.com/jeferagudeloc/grpc-http-gateway.git + +``` + +Navigate to the project directory: + + + +```shell + +cd grpc-http-gateway + +``` + +Build and run the containers using Docker Compose: + + + +```shell + +docker-compose up --build + +``` + +The HTTP gateway service will be accessible at http://localhost:9091. + + +## Project Structure + +The project follows a typical directory structure: + + + +*src/server:* Contains the implementation of the gRPC backend server. + +*src/gateway:* Contains the implementation of the HTTP gateway. + +*config/data:* Contains the SQL scripts for database initialization. + +## Configuration + +The project uses Docker Compose for container orchestration. The services and their configurations are defined in the docker-compose.yml file. + + +***server:*** Builds and runs the gRPC backend server. + +***gateway:*** Builds and runs the HTTP gateway service. + +***mysql:*** Runs the MySQL database server. + +The environment variables for the services can be configured in the docker-compose.yml file. + + +### Contributing + +Contributions are welcome! If you find any issues or have suggestions for improvements, please open an issue or submit a pull request. + + + +### License + +This project is licensed under the MIT License. \ No newline at end of file diff --git a/config/data/data.sql b/config/data/data.sql new file mode 100644 index 0000000..dfe61f0 --- /dev/null +++ b/config/data/data.sql @@ -0,0 +1,17 @@ +-- Insert fake data into the "order" table +INSERT INTO http_gateway_grpc.`order` (`id`, `order_number`, `creation_date`, `updation_date`, `status`) +VALUES + ('76045a01-93e4-45c9-8670-8ebe780056a2 ', 'ORD001', '2023-05-20', '2023-05-21', 'Pending'), + ('8433937f-18a2-4501-ad58-2abc1707aa93 ', 'ORD002', '2023-05-19', '2023-05-20', 'Completed'); + +-- Insert fake data into the "profile" table +INSERT INTO http_gateway_grpc.`profile` (`id`, `name`, `type`) +VALUES + ('1ba3f566-cf9a-4814-99c9-6ceca164c1e2 ', 'Profile 1', 'Type A'), + ('553bfd79-f007-4b9f-a9c3-c926c8588881 ', 'Profile 2', 'Type B'); + +-- Insert fake data into the "user" table +INSERT INTO http_gateway_grpc.`user` (`id`, `name`, `lastname`, `email`, `profile`, `status`, `profile_id`) +VALUES + ('64a498d9-09b3-4b0d-98ed-cc796e6457c5 ', 'John', 'Doe', 'john@example.com', 'User Profile', 'Active', '1ba3f566-cf9a-4814-99c9-6ceca164c1e2 '), + ('e5a1893f-8ff9-4082-966a-ba6824d8fe50 ', 'Jane', 'Smith', 'jane@example.com', 'Admin Profile', 'Inactive', '553bfd79-f007-4b9f-a9c3-c926c8588881 '); diff --git a/config/data/schemas.sql b/config/data/schemas.sql new file mode 100644 index 0000000..48300f5 --- /dev/null +++ b/config/data/schemas.sql @@ -0,0 +1,30 @@ +-- Create a schema +CREATE SCHEMA IF NOT EXISTS http_gateway_grpc; + +-- Create the "order" table in the schema +CREATE TABLE http_gateway_grpc.`order` ( + `id` VARCHAR(50) PRIMARY KEY, + `order_number` VARCHAR(255) NOT NULL, + `creation_date` DATE, + `updation_date` DATE, + `status` VARCHAR(50) +); + +-- Create the "profile" table in the schema +CREATE TABLE http_gateway_grpc.`profile` ( + `id` VARCHAR(50) PRIMARY KEY, + `name` VARCHAR(50) NOT NULL, + `type` VARCHAR(50) +); + +-- Create the "user" table in the schema +CREATE TABLE http_gateway_grpc.`user` ( + `id` VARCHAR(50) PRIMARY KEY, + `name` VARCHAR(50) NOT NULL, + `lastname` VARCHAR(50) NOT NULL, + `email` VARCHAR(255) NOT NULL, + `profile` VARCHAR(50), + `status` VARCHAR(50), + `profile_id` VARCHAR(50), + FOREIGN KEY (`profile_id`) REFERENCES http_gateway_grpc.`profile` (`id`) +); diff --git a/docker-compose.yml b/docker-compose.yml index 790b390..c2d519f 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -7,6 +7,15 @@ services: container_name: server volumes: - ./src/server:/usr/src/app/ + depends_on: + - mysql + restart: on-failure + environment: + - MYSQL_HOST=mysql + - MYSQL_DATABASE=http_gateway_grpc + - MYSQL_PORT=3306 + - MYSQL_USER=root + - MYSQL_PASSWORD=root networks: - bridge gateway: @@ -20,7 +29,21 @@ services: - server restart: on-failure volumes: - - ./src/client:/usr/src/app/ + - ./src/gateway:/usr/src/app/ + networks: + - bridge + mysql: + image: mysql + command: --default-authentication-plugin=mysql_native_password + hostname: mysql + environment: + MYSQL_ROOT_PASSWORD: root + container_name: mysql + ports: + - "3306:3306" + volumes: + - "./config/data/schemas.sql:/docker-entrypoint-initdb.d/1.sql" + - "./config/data/data.sql:/docker-entrypoint-initdb.d/2.sql" networks: - bridge networks: diff --git a/src/gateway/README.md b/src/gateway/README.md deleted file mode 100644 index 99f1089..0000000 --- a/src/gateway/README.md +++ /dev/null @@ -1 +0,0 @@ -# todo \ No newline at end of file diff --git a/src/gateway/application/adapter/api/action/create_order.go b/src/gateway/application/adapter/api/action/create_order.go new file mode 100644 index 0000000..16e19af --- /dev/null +++ b/src/gateway/application/adapter/api/action/create_order.go @@ -0,0 +1,61 @@ +package action + +import ( + "encoding/json" + "io/ioutil" + "net/http" + + "github.com/jeferagudeloc/grpc-http-gateway/src/gateway/application/adapter/api/response" + "github.com/jeferagudeloc/grpc-http-gateway/src/gateway/application/adapter/logger" + "github.com/jeferagudeloc/grpc-http-gateway/src/gateway/application/adapter/logging" + "github.com/jeferagudeloc/grpc-http-gateway/src/gateway/application/usecase" + "github.com/jeferagudeloc/grpc-http-gateway/src/gateway/domain" +) + +type CreateOrderAction struct { + uc usecase.CreateOrderUseCase + log logger.Logger +} + +func NewCreateOrderAction(uc usecase.CreateOrderUseCase, log logger.Logger) CreateOrderAction { + return CreateOrderAction{ + uc: uc, + log: log, + } +} + +func (fova CreateOrderAction) Execute(w http.ResponseWriter, r *http.Request) { + const logKey = "save_device_token" + + body, err := ioutil.ReadAll(r.Body) + if err != nil { + http.Error(w, "Unable to read request body", http.StatusBadRequest) + return + } + + var dt domain.OrderRequest + err = json.Unmarshal(body, &dt) + if err != nil { + http.Error(w, "Unable to parse JSON data", http.StatusBadRequest) + return + } + + output, err := fova.uc.Execute(r.Context(), dt) + if err != nil { + switch err { + default: + logging.NewError( + fova.log, + err, + logKey, + http.StatusInternalServerError, + ).Log("error saving device token") + + response.NewError(err, http.StatusInternalServerError).Send(w) + return + } + } + logging.NewInfo(fova.log, logKey, http.StatusOK).Log("device token has been saved") + + response.NewSuccess(output, http.StatusOK).Send(w) +} diff --git a/src/gateway/application/adapter/api/action/health_check.go b/src/gateway/application/adapter/api/action/health_check.go index 679722f..426f152 100644 --- a/src/gateway/application/adapter/api/action/health_check.go +++ b/src/gateway/application/adapter/api/action/health_check.go @@ -5,4 +5,3 @@ import "net/http" func HealthCheck(w http.ResponseWriter, _ *http.Request) { w.WriteHeader(http.StatusOK) } - diff --git a/src/gateway/application/adapter/api/middleware/logger.go b/src/gateway/application/adapter/api/middleware/logger.go new file mode 100644 index 0000000..5f5062a --- /dev/null +++ b/src/gateway/application/adapter/api/middleware/logger.go @@ -0,0 +1,78 @@ +package middleware + +import ( + "bytes" + "io/ioutil" + "net/http" + "strings" + "time" + + "github.com/jeferagudeloc/grpc-http-gateway/src/gateway/application/adapter/logger" + "github.com/jeferagudeloc/grpc-http-gateway/src/gateway/application/adapter/logging" + "github.com/pkg/errors" + "github.com/urfave/negroni" +) + +type Logger struct { + log logger.Logger +} + +func NewLogger(log logger.Logger) Logger { + return Logger{log: log} +} + +func (l Logger) Execute(w http.ResponseWriter, r *http.Request, next http.HandlerFunc) { + start := time.Now() + + const ( + logKey = "logger_middleware" + requestKey = "api_request" + responseKey = "api_response" + ) + + body, err := getRequestPayload(r) + if err != nil { + logging.NewError( + l.log, + err, + logKey, + http.StatusBadRequest, + ).Log("error when getting payload") + + return + } + + l.log.WithFields(logger.Fields{ + "key": requestKey, + "payload": body, + "url": r.URL.Path, + "http_method": r.Method, + }).Infof("started handling request") + + next.ServeHTTP(w, r) + + end := time.Since(start).Seconds() + res := w.(negroni.ResponseWriter) + l.log.WithFields(logger.Fields{ + "key": responseKey, + "url": r.URL.Path, + "http_method": r.Method, + "http_status": res.Status(), + "response_time": end, + }).Infof("completed handling request") +} + +func getRequestPayload(r *http.Request) (string, error) { + if r.Body == nil { + return "", errors.New("body not defined") + } + + payload, err := ioutil.ReadAll(r.Body) + if err != nil { + return "", errors.Wrap(err, "error read body") + } + + r.Body = ioutil.NopCloser(bytes.NewBuffer(payload)) + + return strings.TrimSpace(string(payload)), nil +} diff --git a/src/gateway/application/adapter/api/response/error.go b/src/gateway/application/adapter/api/response/error.go new file mode 100644 index 0000000..318980d --- /dev/null +++ b/src/gateway/application/adapter/api/response/error.go @@ -0,0 +1,37 @@ +package response + +import ( + "encoding/json" + "net/http" + + "github.com/pkg/errors" +) + +var ( + ErrParameterInvalid = errors.New("parameter invalid") +) + +type Error struct { + statusCode int + Errors []string `json:"errors"` +} + +func NewError(err error, status int) *Error { + return &Error{ + statusCode: status, + Errors: []string{err.Error()}, + } +} + +func NewErrorMessage(messages []string, status int) *Error { + return &Error{ + statusCode: status, + Errors: messages, + } +} + +func (e Error) Send(w http.ResponseWriter) error { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(e.statusCode) + return json.NewEncoder(w).Encode(e) +} diff --git a/src/gateway/application/adapter/api/response/success.go b/src/gateway/application/adapter/api/response/success.go new file mode 100644 index 0000000..623449e --- /dev/null +++ b/src/gateway/application/adapter/api/response/success.go @@ -0,0 +1,24 @@ +package response + +import ( + "encoding/json" + "net/http" +) + +type Success struct { + statusCode int + result interface{} +} + +func NewSuccess(result interface{}, status int) Success { + return Success{ + statusCode: status, + result: result, + } +} + +func (r Success) Send(w http.ResponseWriter) error { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(r.statusCode) + return json.NewEncoder(w).Encode(r.result) +} diff --git a/src/gateway/application/adapter/presenter/create_order.go b/src/gateway/application/adapter/presenter/create_order.go new file mode 100644 index 0000000..9e18520 --- /dev/null +++ b/src/gateway/application/adapter/presenter/create_order.go @@ -0,0 +1,18 @@ +package presenter + +import ( + "github.com/jeferagudeloc/grpc-http-gateway/src/gateway/application/usecase" + "github.com/jeferagudeloc/grpc-http-gateway/src/gateway/domain" +) + +type createOrderPresenter struct{} + +func NewCreateOrderPresenter() usecase.CreateOrderPresenter { + return createOrderPresenter{} +} + +func (a createOrderPresenter) Output(domain domain.Order) usecase.CreateOrderOutput { + return usecase.CreateOrderOutput{ + OrderNumber: domain.OrderNumber, + } +} diff --git a/src/gateway/application/usecase/create_order_gtw.go b/src/gateway/application/usecase/create_order_gtw.go new file mode 100644 index 0000000..ab30928 --- /dev/null +++ b/src/gateway/application/usecase/create_order_gtw.go @@ -0,0 +1,72 @@ +package usecase + +import ( + "context" + "time" + + "github.com/jeferagudeloc/grpc-http-gateway/src/gateway/application/adapter/logger" + "github.com/jeferagudeloc/grpc-http-gateway/src/gateway/domain" + "github.com/jeferagudeloc/grpc-http-gateway/src/server/application/adapter/grpc/order" + "google.golang.org/grpc" +) + +type ( + CreateOrderRequest struct { + OrderNumber int64 `json:"orderNumber"` + } + + CreateOrderUseCase interface { + Execute(context.Context, domain.OrderRequest) (CreateOrderOutput, error) + } + + CreateOrderPresenter interface { + Output(domain.Order) CreateOrderOutput + } + + CreateOrderOutput struct { + OrderNumber int64 `json:"orderNumber"` + } + + CreateOrderInteractor struct { + presenter CreateOrderPresenter + ctxTimeout time.Duration + logger logger.Logger + grpcClient *grpc.ClientConn + } +) + +func NewCreateOrderInteractor( + presenter CreateOrderPresenter, + t time.Duration, + l logger.Logger, + grpcClient *grpc.ClientConn, +) CreateOrderUseCase { + return CreateOrderInteractor{ + presenter: presenter, + ctxTimeout: t, + logger: l, + grpcClient: grpcClient, + } +} + +func (a CreateOrderInteractor) Execute(ctx context.Context, orderRequest domain.OrderRequest) (CreateOrderOutput, error) { + + orderClient := order.NewOrderHandlerClient(a.grpcClient) + + orderProto := order.CreateOrderRequest{ + OrderNumber: orderRequest.OrderNumber, + } + + a.logger.Infof("send order to server: Order Number[%v]", orderProto.OrderNumber) + response, err := orderClient.CreateOrder(context.Background(), &orderProto) + + if err != nil { + a.logger.Fatalln("Error when trying to say hello: %v", err) + } + + a.logger.Infof("response from server: %v", response) + + return a.presenter.Output(domain.Order{ + OrderNumber: response.ID, + }), nil +} diff --git a/src/gateway/domain/order.go b/src/gateway/domain/order.go new file mode 100644 index 0000000..4c80378 --- /dev/null +++ b/src/gateway/domain/order.go @@ -0,0 +1,9 @@ +package domain + +type Order struct { + OrderNumber int64 `json:"orderNumber"` +} + +type OrderRequest struct { + OrderNumber int64 `json:"orderNumber"` +} diff --git a/src/gateway/go.mod b/src/gateway/go.mod index 08a4108..751134c 100644 --- a/src/gateway/go.mod +++ b/src/gateway/go.mod @@ -3,8 +3,12 @@ module github.com/jeferagudeloc/grpc-http-gateway/src/gateway go 1.19 require ( + github.com/gorilla/mux v1.8.0 + github.com/jeferagudeloc/grpc-http-gateway/src/server v0.0.0-20230521043111-a1f7c6b005ed github.com/joho/godotenv v1.5.1 + github.com/pkg/errors v0.9.1 github.com/sirupsen/logrus v1.9.2 + github.com/urfave/negroni v1.0.0 google.golang.org/grpc v1.55.0 ) diff --git a/src/gateway/go.sum b/src/gateway/go.sum index 74bd08a..bad09c8 100644 --- a/src/gateway/go.sum +++ b/src/gateway/go.sum @@ -6,8 +6,14 @@ github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= +github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI= +github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= +github.com/jeferagudeloc/grpc-http-gateway/src/server v0.0.0-20230521043111-a1f7c6b005ed h1:gZIWLnFzR0Ufn/91UR97RiSLkn3P+ShwV/cZyovxjx8= +github.com/jeferagudeloc/grpc-http-gateway/src/server v0.0.0-20230521043111-a1f7c6b005ed/go.mod h1:XWMEqHYMaxoHe0MxTcVhZO79qTaXAMgTrM7evPYGr4k= github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/sirupsen/logrus v1.9.2 h1:oxx1eChJGI6Uks2ZC4W1zpLlVgqB8ner4EuQwV4Ik1Y= @@ -15,6 +21,8 @@ github.com/sirupsen/logrus v1.9.2/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVs github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/urfave/negroni v1.0.0 h1:kIimOitoypq34K7TG7DUaJ9kq/N4Ofuwi1sjz0KipXc= +github.com/urfave/negroni v1.0.0/go.mod h1:Meg73S6kFm/4PpbYdq35yYWoCZ9mS/YSx+lKnmiohz4= golang.org/x/net v0.8.0 h1:Zrh2ngAOFYneWTAIAPethzeaQLuHwhuBkuV6ZiRnUaQ= golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= diff --git a/src/gateway/infrastructure/bootstrap.go b/src/gateway/infrastructure/bootstrap.go index 6b3d4ab..08a2049 100644 --- a/src/gateway/infrastructure/bootstrap.go +++ b/src/gateway/infrastructure/bootstrap.go @@ -5,14 +5,14 @@ import ( "github.com/jeferagudeloc/grpc-http-gateway/src/gateway/application/adapter/logger" "github.com/jeferagudeloc/grpc-http-gateway/src/gateway/infrastructure/log" - "google.golang.org/grpc" - "google.golang.org/grpc/credentials/insecure" + "github.com/jeferagudeloc/grpc-http-gateway/src/gateway/infrastructure/router" ) type config struct { appName string logger logger.Logger ctxTimeout time.Duration + webServer router.Server } func NewConfig() *config { @@ -40,14 +40,23 @@ func (c *config) Logger(instance int) *config { return c } -func (c *config) Start() { +func (c *config) WebServer(instance int) *config { + s, err := router.NewWebServerFactory( + instance, + c.logger, + c.ctxTimeout, + ) - var conn *grpc.ClientConn - conn, err := grpc.Dial("dns:///server:9000", grpc.WithTransportCredentials(insecure.NewCredentials())) if err != nil { - c.logger.Fatalln("could not connect to server: %v", err) + c.logger.Fatalln(err) } - defer conn.Close() + c.logger.Infof("Successfully configured router server") + c.webServer = s + return c +} + +func (c *config) Start() { + c.webServer.Listen() } diff --git a/src/gateway/infrastructure/router/factory.go b/src/gateway/infrastructure/router/factory.go new file mode 100644 index 0000000..28d2f8d --- /dev/null +++ b/src/gateway/infrastructure/router/factory.go @@ -0,0 +1,35 @@ +package router + +import ( + "errors" + "time" + + "github.com/jeferagudeloc/grpc-http-gateway/src/gateway/application/adapter/logger" +) + +type Server interface { + Listen() +} + +type Port int64 + +var ( + errInvalidWebServerInstance = errors.New("invalid router server instance") +) + +const ( + InstanceGorillaMux int = iota +) + +func NewWebServerFactory( + instance int, + log logger.Logger, + ctxTimeout time.Duration, +) (Server, error) { + switch instance { + case InstanceGorillaMux: + return newGorillaMux(log, ctxTimeout), nil + default: + return nil, errInvalidWebServerInstance + } +} diff --git a/src/gateway/infrastructure/router/gorilla_mux.go b/src/gateway/infrastructure/router/gorilla_mux.go new file mode 100644 index 0000000..29c9445 --- /dev/null +++ b/src/gateway/infrastructure/router/gorilla_mux.go @@ -0,0 +1,112 @@ +package router + +import ( + "context" + "fmt" + "net/http" + "os" + "os/signal" + "syscall" + "time" + + "github.com/gorilla/mux" + "google.golang.org/grpc" + "google.golang.org/grpc/credentials/insecure" + + "github.com/jeferagudeloc/grpc-http-gateway/src/gateway/application/adapter/api/action" + "github.com/jeferagudeloc/grpc-http-gateway/src/gateway/application/adapter/api/middleware" + "github.com/jeferagudeloc/grpc-http-gateway/src/gateway/application/adapter/presenter" + "github.com/jeferagudeloc/grpc-http-gateway/src/gateway/application/usecase" + + "github.com/jeferagudeloc/grpc-http-gateway/src/gateway/application/adapter/logger" + "github.com/urfave/negroni" +) + +type gorillaMux struct { + router *mux.Router + middleware *negroni.Negroni + log logger.Logger + ctxTimeout time.Duration +} + +func newGorillaMux( + log logger.Logger, + t time.Duration, +) *gorillaMux { + return &gorillaMux{ + router: mux.NewRouter(), + middleware: negroni.New(), + log: log, + ctxTimeout: t, + } +} + +func (g gorillaMux) Listen() { + g.setAppHandlers(g.router) + g.middleware.UseHandler(g.router) + + server := &http.Server{ + ReadTimeout: 5 * time.Second, + WriteTimeout: 15 * time.Second, + Addr: fmt.Sprintf(":%d", 8080), + Handler: g.middleware, + } + + stop := make(chan os.Signal, 1) + signal.Notify(stop, os.Interrupt, syscall.SIGINT, syscall.SIGTERM) + + go func() { + g.log.WithFields(logger.Fields{"port": 8080}).Infof("Starting HTTP Server") + if err := server.ListenAndServe(); err != nil { + g.log.WithError(err).Fatalln("Error starting HTTP server") + } + }() + + <-stop + + ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) + defer func() { + cancel() + }() + + if err := server.Shutdown(ctx); err != nil { + g.log.WithError(err).Fatalln("Server Shutdown Failed") + } + + g.log.Infof("Service down") +} + +func (g gorillaMux) setAppHandlers(router *mux.Router) { + api := router + api.HandleFunc("/health", action.HealthCheck).Methods(http.MethodGet) + api.Handle("/orders", g.buildCreateOrderAction()).Methods(http.MethodPost) +} + +func (g gorillaMux) buildCreateOrderAction() *negroni.Negroni { + + var conn *grpc.ClientConn + conn, err := grpc.Dial("dns:///server:9000", grpc.WithTransportCredentials(insecure.NewCredentials())) + if err != nil { + g.log.Fatalln("could not connect to server: %v", err) + } + + var handler http.HandlerFunc = func(res http.ResponseWriter, req *http.Request) { + var ( + uc = usecase.NewCreateOrderInteractor( + presenter.NewCreateOrderPresenter(), + g.ctxTimeout, + g.log, + conn, + ) + act = action.NewCreateOrderAction(uc, g.log) + ) + + act.Execute(res, req) + } + + return negroni.New( + negroni.HandlerFunc(middleware.NewLogger(g.log).Execute), + negroni.NewRecovery(), + negroni.Wrap(handler), + ) +} diff --git a/src/gateway/main.go b/src/gateway/main.go index a820c6d..11c5ee7 100644 --- a/src/gateway/main.go +++ b/src/gateway/main.go @@ -6,6 +6,7 @@ import ( "github.com/jeferagudeloc/grpc-http-gateway/src/gateway/infrastructure" "github.com/jeferagudeloc/grpc-http-gateway/src/gateway/infrastructure/log" + "github.com/jeferagudeloc/grpc-http-gateway/src/gateway/infrastructure/router" "github.com/joho/godotenv" ) @@ -15,7 +16,8 @@ func main() { var app = infrastructure.NewConfig(). Name(os.Getenv("APP_NAME")). ContextTimeout(10 * time.Second). - Logger(log.InstanceLogrusLogger) + Logger(log.InstanceLogrusLogger). + WebServer(router.InstanceGorillaMux) app.Start() diff --git a/src/server/.env b/src/server/.env new file mode 100644 index 0000000..7d9c7fa --- /dev/null +++ b/src/server/.env @@ -0,0 +1,5 @@ +MYSQL_HOST=mysql +MYSQL_DATABASE=http_gateway_grpc +MYSQL_PORT=3306 +MYSQL_USER=root +MYSQL_PASSWORD=root \ No newline at end of file diff --git a/src/server/.vscode/launch.json b/src/server/.vscode/launch.json new file mode 100644 index 0000000..bbf3fc1 --- /dev/null +++ b/src/server/.vscode/launch.json @@ -0,0 +1,16 @@ +{ + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "name": "server", + "type": "go", + "request": "launch", + "mode": "auto", + "program": "${workspaceFolder}/main.go", + "args": [], + } + ] +} \ No newline at end of file diff --git a/src/server/README.md b/src/server/README.md deleted file mode 100644 index 99f1089..0000000 --- a/src/server/README.md +++ /dev/null @@ -1 +0,0 @@ -# todo \ No newline at end of file diff --git a/src/server/application/adapter/grpc/order/order.go b/src/server/application/adapter/grpc/order/order.go index c79ab4c..3b840e4 100644 --- a/src/server/application/adapter/grpc/order/order.go +++ b/src/server/application/adapter/grpc/order/order.go @@ -6,7 +6,6 @@ import ( "log" "github.com/jeferagudeloc/grpc-http-gateway/src/server/application/usecase" - "github.com/jeferagudeloc/grpc-http-gateway/src/server/domain" ) type Server struct { @@ -14,6 +13,13 @@ type Server struct { getOrderUseCase usecase.GetOrderUseCase } +func NewServer(createOrderUseCase usecase.CreateOrderUseCase, getOrderUseCase usecase.GetOrderUseCase) *Server { + return &Server{ + createOrderUseCase: createOrderUseCase, + getOrderUseCase: getOrderUseCase, + } +} + func (s *Server) CreateOrder(ctx context.Context, req *CreateOrderRequest) (*Order, error) { output, err := s.createOrderUseCase.Execute(ctx, usecase.CreateOrderInput{ OrderNumber: req.OrderNumber, @@ -30,7 +36,6 @@ func (s *Server) GetOrder(ctx context.Context, req *GetOrderRequest) (*Order, er output, err := s.getOrderUseCase.Execute(ctx, usecase.GetOrderInput{ OrderNumber: fmt.Sprint(req.OrderNumber), - Products: make([]domain.Product, 0), }) if err != nil { diff --git a/src/server/application/adapter/model/order.go b/src/server/application/adapter/model/order.go new file mode 100644 index 0000000..275c4d4 --- /dev/null +++ b/src/server/application/adapter/model/order.go @@ -0,0 +1,15 @@ +package model + +import "time" + +type Order struct { + ID string `gorm:"primaryKey"` + OrderNumber string `gorm:"not null"` + CreationDate time.Time + UpdationDate time.Time + Status string +} + +func (Order) TableName() string { + return "order" +} diff --git a/src/server/application/adapter/model/user.go b/src/server/application/adapter/model/user.go new file mode 100644 index 0000000..e2904dc --- /dev/null +++ b/src/server/application/adapter/model/user.go @@ -0,0 +1,18 @@ +package model + +type Profile struct { + ID string `gorm:"primaryKey"` + Name string `gorm:"not null"` + Type string +} + +type User struct { + ID string `gorm:"primaryKey"` + Name string `gorm:"not null"` + LastName string `gorm:"not null"` + Email string `gorm:"not null"` + Profile string + Status string + ProfileID string + ProfileRef Profile `gorm:"foreignKey:ProfileID"` +} diff --git a/src/server/application/adapter/presenter/create_order.go b/src/server/application/adapter/presenter/create_order.go new file mode 100644 index 0000000..1af9c20 --- /dev/null +++ b/src/server/application/adapter/presenter/create_order.go @@ -0,0 +1,15 @@ +package presenter + +import "github.com/jeferagudeloc/grpc-http-gateway/src/server/application/usecase" + +type createOrderPresenter struct{} + +func NewCreateOrderPresenter() usecase.CreateOrderPresenter { + return createOrderPresenter{} +} + +func (a createOrderPresenter) Output(saved bool) usecase.CreateOrderOutput { + return usecase.CreateOrderOutput{ + Saved: saved, + } +} diff --git a/src/server/application/adapter/repository/mysql_handler.go b/src/server/application/adapter/repository/mysql_handler.go new file mode 100644 index 0000000..d289759 --- /dev/null +++ b/src/server/application/adapter/repository/mysql_handler.go @@ -0,0 +1,42 @@ +package repository + +import ( + "context" + + "github.com/google/uuid" + "github.com/jeferagudeloc/grpc-http-gateway/src/server/application/adapter/model" + "github.com/jeferagudeloc/grpc-http-gateway/src/server/domain" + "github.com/pkg/errors" +) + +type MysqlSQL struct { + db SQL +} + +func NewMysqlSQL(db SQL) MysqlSQL { + return MysqlSQL{ + db: db, + } +} + +func (a MysqlSQL) SaveOrder(ctx context.Context, order domain.OrderData) (domain.OrderData, error) { + + orderEntity := model.Order{ + ID: uuid.New().String(), + OrderNumber: order.OrderNumber, + CreationDate: order.CreationDate, + UpdationDate: order.UpdationDate, + Status: order.Status, + } + + _, err := a.db.SaveOrder( + ctx, + orderEntity, + ) + + if err != nil { + return order, errors.Wrap(err, "error creating account") + } + + return order, nil +} diff --git a/src/server/application/adapter/repository/sql.go b/src/server/application/adapter/repository/sql.go new file mode 100644 index 0000000..71bbc18 --- /dev/null +++ b/src/server/application/adapter/repository/sql.go @@ -0,0 +1,11 @@ +package repository + +import ( + "context" + + "github.com/jeferagudeloc/grpc-http-gateway/src/server/application/adapter/model" +) + +type SQL interface { + SaveOrder(context.Context, model.Order) (*model.Order, error) +} diff --git a/src/server/application/usecase/create_order.go b/src/server/application/usecase/create_order.go index 1d6bb65..df38192 100644 --- a/src/server/application/usecase/create_order.go +++ b/src/server/application/usecase/create_order.go @@ -15,8 +15,7 @@ type ( } CreateOrderInput struct { - OrderNumber int64 `json:"orderNumber" validate:"required"` - Products []domain.Product `json:"products" validate:"required"` + OrderNumber int64 `json:"orderNumber" validate:"required"` } CreateOrderPresenter interface { @@ -28,32 +27,31 @@ type ( } CreateOrderInteractor struct { - repo domain.OrderRepository - presenter CreateOrderPresenter - ctxTimeout time.Duration + repo domain.OrderRepository + presenter CreateOrderPresenter } ) func NewCreateOrderInteractor( repo domain.OrderRepository, presenter CreateOrderPresenter, - t time.Duration, ) CreateOrderUseCase { return CreateOrderInteractor{ - repo: repo, - presenter: presenter, - ctxTimeout: t, + repo: repo, + presenter: presenter, } } func (a CreateOrderInteractor) Execute(ctx context.Context, order CreateOrderInput) (CreateOrderOutput, error) { orderDataToSave := domain.OrderData{ - OrderNumber: fmt.Sprint(order.OrderNumber), - Products: order.Products, + OrderNumber: fmt.Sprint(order.OrderNumber), + CreationDate: time.Now(), + UpdationDate: time.Now(), + Status: "idle", } - orderDataSaved, err := a.repo.SaveOrder(orderDataToSave) + orderDataSaved, err := a.repo.SaveOrder(ctx, orderDataToSave) if err != nil { return a.presenter.Output(false), err } diff --git a/src/server/application/usecase/get_order.go b/src/server/application/usecase/get_order.go index aa44878..0a51db0 100644 --- a/src/server/application/usecase/get_order.go +++ b/src/server/application/usecase/get_order.go @@ -5,7 +5,6 @@ import ( "time" "github.com/jeferagudeloc/grpc-http-gateway/src/server/domain" - "github.com/sirupsen/logrus" ) type ( @@ -14,8 +13,7 @@ type ( } GetOrderInput struct { - OrderNumber string `json:"orderNumber" validate:"required"` - Products []domain.Product `json:"products" validate:"required"` + OrderNumber string `json:"orderNumber" validate:"required"` } GetOrderPresenter interface { @@ -46,18 +44,5 @@ func NewGetOrderInteractor( } func (a GetOrderInteractor) Execute(ctx context.Context, order GetOrderInput) (GetOrderOutput, error) { - - orderDataToSave := domain.OrderData{ - OrderNumber: order.OrderNumber, - Products: order.Products, - } - - orderDataSaved, err := a.repo.SaveOrder(orderDataToSave) - if err != nil { - return a.presenter.Output(false), err - } - - logrus.Info("orderDataSaved", orderDataSaved) - return a.presenter.Output(true), nil } diff --git a/src/server/domain/order.go b/src/server/domain/order.go index 3e135c1..89c9c37 100644 --- a/src/server/domain/order.go +++ b/src/server/domain/order.go @@ -1,17 +1,19 @@ package domain +import ( + "context" + "time" +) + type ( OrderRepository interface { - SaveOrder(OrderData) (OrderData, error) - } - - Product struct { - SKU string - Name string + SaveOrder(context.Context, OrderData) (OrderData, error) } OrderData struct { - OrderNumber string - Products []Product + OrderNumber string + CreationDate time.Time + UpdationDate time.Time + Status string } ) diff --git a/src/server/domain/user.go b/src/server/domain/user.go index 0f8ed5a..52b8ab7 100644 --- a/src/server/domain/user.go +++ b/src/server/domain/user.go @@ -1,8 +1,10 @@ package domain +import "github.com/jeferagudeloc/grpc-http-gateway/src/server/application/adapter/model" + type ( UserRepository interface { - SaveUser(UserData) (UserData, error) + SaveUser(UserData) (model.Order, error) } UserData struct { diff --git a/src/server/go.mod b/src/server/go.mod index e340a13..4d7f813 100644 --- a/src/server/go.mod +++ b/src/server/go.mod @@ -3,17 +3,23 @@ module github.com/jeferagudeloc/grpc-http-gateway/src/server go 1.19 require ( + github.com/google/uuid v1.3.0 + github.com/joho/godotenv v1.5.1 + github.com/pkg/errors v0.9.1 github.com/sirupsen/logrus v1.9.2 google.golang.org/grpc v1.55.0 + google.golang.org/protobuf v1.30.0 + gorm.io/driver/mysql v1.5.1 + gorm.io/gorm v1.25.1 ) require ( + github.com/go-sql-driver/mysql v1.7.0 // indirect github.com/golang/protobuf v1.5.3 // indirect - github.com/joho/godotenv v1.5.1 // indirect + github.com/jinzhu/inflection v1.0.0 // indirect + github.com/jinzhu/now v1.1.5 // indirect golang.org/x/net v0.8.0 // indirect golang.org/x/sys v0.6.0 // indirect golang.org/x/text v0.8.0 // indirect google.golang.org/genproto v0.0.0-20230306155012-7f2fa6fef1f4 // indirect - google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.3.0 // indirect - google.golang.org/protobuf v1.30.0 // indirect ) diff --git a/src/server/go.sum b/src/server/go.sum index b259bac..5df919f 100644 --- a/src/server/go.sum +++ b/src/server/go.sum @@ -1,13 +1,23 @@ github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/go-sql-driver/mysql v1.7.0 h1:ueSltNNllEqE3qcWBTD0iQd3IpL/6U+mJxLkazJ7YPc= +github.com/go-sql-driver/mysql v1.7.0/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI= github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= +github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= +github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E= +github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= +github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ= +github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/sirupsen/logrus v1.9.2 h1:oxx1eChJGI6Uks2ZC4W1zpLlVgqB8ner4EuQwV4Ik1Y= @@ -27,8 +37,6 @@ google.golang.org/genproto v0.0.0-20230306155012-7f2fa6fef1f4 h1:DdoeryqhaXp1LtT google.golang.org/genproto v0.0.0-20230306155012-7f2fa6fef1f4/go.mod h1:NWraEVixdDnqcqQ30jipen1STv2r/n24Wb7twVTGR4s= google.golang.org/grpc v1.55.0 h1:3Oj82/tFSCeUrRTg/5E/7d/W5A1tj6Ky1ABAuZuv5ag= google.golang.org/grpc v1.55.0/go.mod h1:iYEXKGkEBhg1PjZQvoYEVPTDkHo1/bjTnfwTeGONTY8= -google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.3.0 h1:rNBFJjBCOgVr9pWD7rs/knKL4FRTKgpZmsRfV214zcA= -google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.3.0/go.mod h1:Dk1tviKTvMCz5tvh7t+fh94dhmQVHuCt2OzJB3CTW9Y= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.30.0 h1:kPPoIgf3TsEvrm0PFe15JQ+570QVxYzEvvHqChK+cng= @@ -36,3 +44,7 @@ google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqw gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gorm.io/driver/mysql v1.5.1 h1:WUEH5VF9obL/lTtzjmML/5e6VfFR/788coz2uaVCAZw= +gorm.io/driver/mysql v1.5.1/go.mod h1:Jo3Xu7mMhCyj8dlrb3WoCaRd1FhsVh+yMXb1jUInf5o= +gorm.io/gorm v1.25.1 h1:nsSALe5Pr+cM3V1qwwQ7rOkw+6UeLrX5O4v3llhHa64= +gorm.io/gorm v1.25.1/go.mod h1:L4uxeKpfBml98NYqVqwAdmV1a2nBtAec/cf3fpucW/k= diff --git a/src/server/infrastructure/bootstrap.go b/src/server/infrastructure/bootstrap.go index 6895aec..f66219d 100644 --- a/src/server/infrastructure/bootstrap.go +++ b/src/server/infrastructure/bootstrap.go @@ -4,6 +4,8 @@ import ( "time" "github.com/jeferagudeloc/grpc-http-gateway/src/server/application/adapter/logger" + "github.com/jeferagudeloc/grpc-http-gateway/src/server/application/adapter/repository" + "github.com/jeferagudeloc/grpc-http-gateway/src/server/infrastructure/database" "github.com/jeferagudeloc/grpc-http-gateway/src/server/infrastructure/log" "github.com/jeferagudeloc/grpc-http-gateway/src/server/infrastructure/server" ) @@ -12,6 +14,7 @@ type config struct { appName string logger logger.Logger ctxTimeout time.Duration + sql repository.SQL server server.Server } @@ -29,6 +32,18 @@ func (c *config) Name(name string) *config { return c } +func (c *config) SqlSetup(instance int) *config { + db, err := database.NewDatabaseSQLFactory(instance) + if err != nil { + c.logger.Fatalln(err, "There was an error setting the database") + } + + c.logger.Infof("Successfully connected to the SQL database") + + c.sql = db + return c +} + func (c *config) Logger(instance int) *config { log, err := log.NewLoggerFactory(instance) if err != nil { @@ -44,6 +59,7 @@ func (c *config) GrpcServer(instance int) *config { s, err := server.NewGrpcServerFactory( instance, c.logger, + c.sql, ) if err != nil { diff --git a/src/server/infrastructure/database/config.go b/src/server/infrastructure/database/config.go new file mode 100644 index 0000000..d22a1e1 --- /dev/null +++ b/src/server/infrastructure/database/config.go @@ -0,0 +1,23 @@ +package database + +import ( + "os" +) + +type config struct { + host string + database string + port string + user string + password string +} + +func newConfigMysql() *config { + return &config{ + host: os.Getenv("MYSQL_HOST"), + database: os.Getenv("MYSQL_DATABASE"), + port: os.Getenv("MYSQL_PORT"), + user: os.Getenv("MYSQL_USER"), + password: os.Getenv("MYSQL_PASSWORD"), + } +} diff --git a/src/server/infrastructure/database/factory_sql.go b/src/server/infrastructure/database/factory_sql.go new file mode 100644 index 0000000..f7ec1ec --- /dev/null +++ b/src/server/infrastructure/database/factory_sql.go @@ -0,0 +1,24 @@ +package database + +import ( + "errors" + + "github.com/jeferagudeloc/grpc-http-gateway/src/server/application/adapter/repository" +) + +var ( + errInvalidSQLDatabaseInstance = errors.New("invalid sql db instance") +) + +const ( + InstanceMysql int = iota +) + +func NewDatabaseSQLFactory(instance int) (repository.SQL, error) { + switch instance { + case InstanceMysql: + return NewMysqlHandler(newConfigMysql()) + default: + return nil, errInvalidSQLDatabaseInstance + } +} diff --git a/src/server/infrastructure/database/mysql_handler.go b/src/server/infrastructure/database/mysql_handler.go new file mode 100644 index 0000000..0a3ceb5 --- /dev/null +++ b/src/server/infrastructure/database/mysql_handler.go @@ -0,0 +1,65 @@ +package database + +import ( + "context" + "errors" + "fmt" + + "github.com/jeferagudeloc/grpc-http-gateway/src/server/application/adapter/model" + "gorm.io/driver/mysql" + "gorm.io/gorm" +) + +type IMysqlHandler interface { + GetConnection() *gorm.DB +} + +type MysqlHandler struct { + db *gorm.DB +} + +func NewMysqlHandler(c *config) (*MysqlHandler, error) { + var dsn = fmt.Sprintf( + "%s:%s@tcp(%s:%s)/%s?charset=utf8&parseTime=True&loc=Local", + c.user, + c.password, + c.host, + c.port, + c.database, + ) + + db, err := gorm.Open(mysql.New(mysql.Config{ + DSN: dsn, + DefaultStringSize: 256, + DisableDatetimePrecision: true, + DontSupportRenameIndex: true, + DontSupportRenameColumn: true, + SkipInitializeWithVersion: false, + }), &gorm.Config{}) + + if err != nil { + panic(err) + } + + sqlDB, err := db.DB() + + if err != nil { + fmt.Errorf("there was an error creating database", err) + } + + sqlDB.SetMaxIdleConns(5) + sqlDB.SetMaxOpenConns(10) + + return &MysqlHandler{db}, nil +} + +func (mysqlHandler MysqlHandler) SaveOrder(ctx context.Context, orderToSave model.Order) (*model.Order, error) { + + result := mysqlHandler.db.Create(&orderToSave) + + if result.Error != nil { + return nil, errors.New("there was an error saving the order") + } + + return &orderToSave, nil +} diff --git a/src/server/infrastructure/server/factory.go b/src/server/infrastructure/server/factory.go index fe25f81..c0480a6 100644 --- a/src/server/infrastructure/server/factory.go +++ b/src/server/infrastructure/server/factory.go @@ -4,6 +4,7 @@ import ( "errors" "github.com/jeferagudeloc/grpc-http-gateway/src/server/application/adapter/logger" + "github.com/jeferagudeloc/grpc-http-gateway/src/server/application/adapter/repository" ) type Server interface { @@ -23,10 +24,11 @@ const ( func NewGrpcServerFactory( instance int, log logger.Logger, + sql repository.SQL, ) (Server, error) { switch instance { case InstanceGRPC: - return NewGRPCServer(log), nil + return NewGRPCServer(log, sql), nil default: return nil, errInvalidServerInstance } diff --git a/src/server/infrastructure/server/server.go b/src/server/infrastructure/server/server.go index b3686bc..80486fe 100644 --- a/src/server/infrastructure/server/server.go +++ b/src/server/infrastructure/server/server.go @@ -9,17 +9,23 @@ import ( "github.com/jeferagudeloc/grpc-http-gateway/src/server/application/adapter/grpc/order" "github.com/jeferagudeloc/grpc-http-gateway/src/server/application/adapter/grpc/user" "github.com/jeferagudeloc/grpc-http-gateway/src/server/application/adapter/logger" + "github.com/jeferagudeloc/grpc-http-gateway/src/server/application/adapter/presenter" + "github.com/jeferagudeloc/grpc-http-gateway/src/server/application/adapter/repository" + "github.com/jeferagudeloc/grpc-http-gateway/src/server/application/usecase" ) type grpcServer struct { log logger.Logger + sql repository.SQL } func NewGRPCServer( log logger.Logger, + sql repository.SQL, ) *grpcServer { return &grpcServer{ log: log, + sql: sql, } } @@ -32,11 +38,18 @@ func (s *grpcServer) Listen() { // main server grpcServer := grpc.NewServer() + var ( + uc = usecase.NewCreateOrderInteractor( + repository.NewMysqlSQL(s.sql), + presenter.NewCreateOrderPresenter(), + ) + ) + // server declarations - orderServer := order.Server{} + orderServer := order.NewServer(uc, nil) userServer := user.Server{} - order.RegisterOrderHandlerServer(grpcServer, &orderServer) + order.RegisterOrderHandlerServer(grpcServer, orderServer) user.RegisterUserHandlerServer(grpcServer, &userServer) if err := grpcServer.Serve(list); err != nil { diff --git a/src/server/main.go b/src/server/main.go index 8399833..637b2e7 100644 --- a/src/server/main.go +++ b/src/server/main.go @@ -5,6 +5,7 @@ import ( "time" "github.com/jeferagudeloc/grpc-http-gateway/src/server/infrastructure" + "github.com/jeferagudeloc/grpc-http-gateway/src/server/infrastructure/database" "github.com/jeferagudeloc/grpc-http-gateway/src/server/infrastructure/log" "github.com/jeferagudeloc/grpc-http-gateway/src/server/infrastructure/server" "github.com/joho/godotenv" @@ -16,7 +17,8 @@ func main() { var app = infrastructure.NewConfig(). Name(os.Getenv("APP_NAME")). ContextTimeout(10 * time.Second). - Logger(log.InstanceLogrusLogger) + Logger(log.InstanceLogrusLogger). + SqlSetup(database.InstanceMysql) app.GrpcServer(server.InstanceGRPC).StartGrpc()