Skip to content

Commit

Permalink
feat: add documentation and services validation (#1)
Browse files Browse the repository at this point in the history
  • Loading branch information
almostinf authored Dec 5, 2023
1 parent 11850de commit 5e1236a
Show file tree
Hide file tree
Showing 22 changed files with 12,011 additions and 178 deletions.
6 changes: 5 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,17 @@ install-mockgen: bindir

gen-mocks: install-mockgen
rm -r order/internal/mocks/repo/
${MOCKGEN} -source=order/internal/infrastructure/interfaces/order.go -destination=order/internal/mocks/repo/order_mocks.go
${MOCKGEN} -source=user/internal/infrastructure/interfaces/user.go -destination=user/internal/mocks/repo/user_mocks.go
${MOCKGEN} -source=product/internal/infrastructure/interfaces/product.go -destination=product/internal/mocks/repo/product_mocks.go
${MOCKGEN} -source=product/internal/infrastructure/interfaces/discount.go -destination=product/internal/mocks/repo/discount_mocks.go
${MOCKGEN} -source=cart/internal/infrastructure/interfaces/cart.go -destination=cart/internal/mocks/repo/cart_mocks.go
${MOCKGEN} -source=cart/internal/infrastructure/interfaces/cart_task.go -destination=cart/internal/mocks/repo/cart_task_mocks.go
${MOCKGEN} -source=order/internal/infrastructure/interfaces/order.go -destination=order/internal/mocks/repo/order_mocks.go

${MOCKGEN} -source=user/internal/usecase/user.go -destination=user/internal/mocks/usecase/user_mocks.go
${MOCKGEN} -source=product/internal/usecase/product.go -destination=product/internal/mocks/usecase/product_mocks.go
${MOCKGEN} -source=cart/internal/usecase/cart.go -destination=cart/internal/mocks/usecase/cart_mocks.go
${MOCKGEN} -source=order/internal/usecase/order.go -destination=order/internal/mocks/usecase/order_mocks.go
.PHONY: gen-mocks

install-lint: bindir
Expand Down
79 changes: 77 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,2 +1,77 @@
# backend
Application backend using microservice architecture in golang
# Go-Marketplace backend

## Quickstart

To run app via docker containers, use the command:
```bash
docker compose up
```

If you want to use nightly images, you can pull and run all images from [dockerhub](https://hub.docker.com/):
- [cart-nightly](https://hub.docker.com/repository/docker/almostinf/go-marketplace-cart-nightly/general)
- [user-nightly](https://hub.docker.com/repository/docker/almostinf/go-marketplace-user-nightly/general)
- [product-nightly](https://hub.docker.com/repository/docker/almostinf/go-marketplace-product-nightly/general)
- [order-nightly](https://hub.docker.com/repository/docker/almostinf/go-marketplace-order-nightly/general)
- [gateway-nightly](https://hub.docker.com/repository/docker/almostinf/go-marketplace-gateway-nightly/general)

To run unit tests:
```bash
make test
```

## Architecture

The backend features an intricate microservices architecture, with each service having its own database and seamless interaction through APIs. For detailed APIs, check the `/proto` folder

The server component uses the [grpc protocol](https://github.com/grpc/grpc-go), enabling exclusive communication among microservices through grpc. Users can choose between grpc and the Restful API via the [grpc-gateway](https://github.com/grpc-ecosystem/grpc-gateway) mechanism

- The user service is vital for storing and modifying user information

- The cart service manages cart details and items, addressing prolonged product storage with a worker. The worker, accessing Redis, cleans up the cart and returns products periodically

- The product service is key for managing product information, ensuring product deletions reflect in associated cart items. Also the service stores information about the discount in Redis with a user-defined life time

- The order service oversees order data, allowing status changes and user order cancellations within 24 hours. Upon order or part deletion, all products are returned

- The gateway service acts as a user facade and authorizes requests, directing them to the necessary microservices for streamlined system functionality

## Docs

All project documentation is in the `/docs` folder, e.g. swagger documentation, ER diagrams and so on

Also, when you run the application under the path `/api/v1/swagger`, you can see the swagger ui and play around with the application api

## Dependencies

[gprc protocol](https://github.com/grpc/grpc-go) was used for server and client side, also from grpc ecosystem I used [grpc-gateway](https://github.com/grpc-ecosystem/grpc-gateway) and [protoc](https://grpc.io/docs/protoc-installation/)

**For unit tests:**
- [testify](https://github.com/stretchr/testify)
- [gomock](https://github.com/golang/mock)

**To work with the databases:**
- PostgreSQL:
- [pgx](https://github.com/jackc/pgx)
- [goose](https://github.com/pressly/goose)
- [squirrel](https://github.com/Masterminds/squirrel)
- Redis:
- [go-redis](https://github.com/redis/go-redis)

**For working with configuration files:** [cleanenv](https://github.com/ilyakaznacheev/cleanenv)

**Logger:** [zerolog](https://github.com/rs/zerolog)

**Linters**:
- [Smart Imports](https://github.com/pav5000/smartimports)
- [Golang-ci lint](https://golangci-lint.run/)

**Swagger**: [swaggerui](https://github.com/flowchartsman/swaggerui)

**RBAC**: [gorbac](https://github.com/mikespook/gorbac)

**Validation**: [validator](https://github.com/go-playground/validator)

**JWT**: [jwt-go](https://github.com/golang-jwt/jwt)

## License
- MIT license ([LICENSE-MIT](https://github.com/seanmonstar/httparse/blob/master/LICENSE-MIT) or [https://opensource.org/licenses/MIT](https://opensource.org/licenses/MIT))
22 changes: 13 additions & 9 deletions cart/internal/api/grpc/controller/cart.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import (
"google.golang.org/grpc/status"
)

func GetUserCart(ctx context.Context, cartUsecase *usecase.CartUsecase, req *pbCart.GetUserCartRequest) (*model.Cart, error) {
func GetUserCart(ctx context.Context, cartUsecase usecase.ICartUsecase, req *pbCart.GetUserCartRequest) (*model.Cart, error) {
if req == nil {
return nil, status.Errorf(codes.InvalidArgument, "Invalid request")
}
Expand All @@ -36,7 +36,7 @@ func GetUserCart(ctx context.Context, cartUsecase *usecase.CartUsecase, req *pbC
return cart, nil
}

func CreateCart(ctx context.Context, cartUsecase *usecase.CartUsecase, req *pbCart.CreateCartRequest) (*model.Cart, error) {
func CreateCart(ctx context.Context, cartUsecase usecase.ICartUsecase, req *pbCart.CreateCartRequest) (*model.Cart, error) {
if req == nil {
return nil, status.Errorf(codes.InvalidArgument, "Invalid request")
}
Expand All @@ -61,7 +61,7 @@ func CreateCart(ctx context.Context, cartUsecase *usecase.CartUsecase, req *pbCa

func CreateCartline(
ctx context.Context,
cartUsecase *usecase.CartUsecase,
cartUsecase usecase.ICartUsecase,
productClient pbProduct.ProductClient,
req *pbCart.CreateCartlineRequest,
) (*model.CartLine, error) {
Expand Down Expand Up @@ -168,7 +168,7 @@ func returnProducts(ctx context.Context, productClient pbProduct.ProductClient,

func UpdateCartline(
ctx context.Context,
cartUsecase *usecase.CartUsecase,
cartUsecase usecase.ICartUsecase,
productClient pbProduct.ProductClient,
req *pbCart.UpdateCartlineRequest,
) (*model.CartLine, error) {
Expand Down Expand Up @@ -201,6 +201,10 @@ func UpdateCartline(
Quantity: req.Quantity,
}

if err = newCartline.Validate(); err != nil {
return nil, status.Errorf(codes.InvalidArgument, "Invalid cartline request: %s", err)
}

cartline, err := cartUsecase.UpdateCartline(ctx, newCartline)
if err != nil {
return nil, status.Errorf(codes.Internal, "Failed to update cartline: %s", err)
Expand Down Expand Up @@ -233,7 +237,7 @@ func UpdateCartline(

func DeleteCart(
ctx context.Context,
cartUsecase *usecase.CartUsecase,
cartUsecase usecase.ICartUsecase,
productClient pbProduct.ProductClient,
req *pbCart.DeleteCartRequest,
) error {
Expand Down Expand Up @@ -274,7 +278,7 @@ func DeleteCart(

func DeleteCartline(
ctx context.Context,
cartUsecase *usecase.CartUsecase,
cartUsecase usecase.ICartUsecase,
productClient pbProduct.ProductClient,
req *pbCart.DeleteCartlineRequest,
) error {
Expand Down Expand Up @@ -311,7 +315,7 @@ func DeleteCartline(
return nil
}

func DeleteProductCartlines(ctx context.Context, cartUsecase *usecase.CartUsecase, req *pbCart.DeleteProductCartlinesRequest) error {
func DeleteProductCartlines(ctx context.Context, cartUsecase usecase.ICartUsecase, req *pbCart.DeleteProductCartlinesRequest) error {
productID, err := uuid.Parse(req.ProductId)
if err != nil {
return status.Errorf(codes.InvalidArgument, "Invalid product id: %s", err)
Expand All @@ -326,7 +330,7 @@ func DeleteProductCartlines(ctx context.Context, cartUsecase *usecase.CartUsecas

func DeleteCartCartlines(
ctx context.Context,
cartUsecase *usecase.CartUsecase,
cartUsecase usecase.ICartUsecase,
productClient pbProduct.ProductClient,
req *pbCart.DeleteCartCartlinesRequest,
) error {
Expand Down Expand Up @@ -364,7 +368,7 @@ func DeleteCartCartlines(

func PrepareOrder(
ctx context.Context,
cartUsecase *usecase.CartUsecase,
cartUsecase usecase.ICartUsecase,
productClient pbProduct.ProductClient,
req *pbCart.PrepareOrderRequest,
) error {
Expand Down
100 changes: 100 additions & 0 deletions cart/internal/api/grpc/controller/cart_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
package controller_test

import (
"context"
"errors"
"testing"

"github.com/Go-Marketplace/backend/cart/internal/api/grpc/controller"
mocks "github.com/Go-Marketplace/backend/cart/internal/mocks/usecase"
"github.com/Go-Marketplace/backend/cart/internal/model"
pbCart "github.com/Go-Marketplace/backend/proto/gen/cart"
"github.com/golang/mock/gomock"
"github.com/google/uuid"
"github.com/stretchr/testify/assert"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
)

func cartHelper(t *testing.T) *mocks.MockICartUsecase {
t.Helper()

mockCtrl := gomock.NewController(t)
defer mockCtrl.Finish()

return mocks.NewMockICartUsecase(mockCtrl)
}

func TestGetUserCart(t *testing.T) {
t.Parallel()

type args struct {
ctx context.Context
req *pbCart.GetUserCartRequest
}

ctx := context.Background()
userID := uuid.New()

expectedCartFromUsecase := &model.Cart{
UserID: userID,
}
expectedErrFromUsecase := errors.New("test error")

testcases := []struct {
name string
args args
mock func(usecase *mocks.MockICartUsecase)
expectedCart *model.Cart
expectedErr error
}{
{
name: "Successfully get cart",
args: args{
ctx: ctx,
req: &pbCart.GetUserCartRequest{
UserId: userID.String(),
},
},
mock: func(usecase *mocks.MockICartUsecase) {
usecase.EXPECT().GetUserCart(ctx, userID).Return(expectedCartFromUsecase, nil).Times(1)
},
expectedCart: expectedCartFromUsecase,
expectedErr: nil,
},
{
name: "Got error when get cart",
args: args{
ctx: ctx,
req: &pbCart.GetUserCartRequest{
UserId: userID.String(),
},
},
mock: func(usecase *mocks.MockICartUsecase) {
usecase.EXPECT().GetUserCart(ctx, userID).Return(nil, expectedErrFromUsecase).Times(1)
},
expectedCart: nil,
expectedErr: status.Errorf(codes.Internal, "Failed to get user cart: %s", expectedErrFromUsecase),
},
}

for _, testcase := range testcases {
testcase := testcase

t.Run(testcase.name, func(t *testing.T) {
t.Parallel()

cartUsecase := cartHelper(t)
testcase.mock(cartUsecase)

actualProduct, actualErr := controller.GetUserCart(
testcase.args.ctx,
cartUsecase,
testcase.args.req,
)

assert.Equal(t, testcase.expectedCart, actualProduct)
assert.Equal(t, testcase.expectedErr, actualErr)
})
}
}
Loading

0 comments on commit 5e1236a

Please sign in to comment.