Skip to content

Commit

Permalink
Simplify router handlers.
Browse files Browse the repository at this point in the history
Adjustments at infra side.
  • Loading branch information
tnsoftbear committed Oct 30, 2024
1 parent 7bb43bd commit 8939487
Show file tree
Hide file tree
Showing 9 changed files with 122 additions and 78 deletions.
38 changes: 31 additions & 7 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -3,21 +3,41 @@ af = -f deploy/docker/compose-api-test.yaml
COVER_FILE ?= coverage.out
REPORT_FILE ?= coverage.html

# Run in docker

init: ## Initialize environement variables
@if [ ! -f ./deploy/docker/.env ]; then \
cp ./deploy/docker/.env.sample ./deploy/docker/.env; \
echo "Adjust configuration in ./deploy/docker/.env"; \
fi;
build: ## Build docker containers
docker compose $(cf) build
up: ## Start docker containers
up: init ## Start docker containers
docker compose $(cf) up -d --remove-orphans
down: ## Stop docker containers
docker compose $(cf) down
rebuild: ## Rebuild and start docker containers
@make down
@make build
@make up
api-test: ## Build and start docker services and run API testing on them
restart: ## Restart docker containers
docker compose $(cf) restart

# Hurl API testing in docker

apitestbuild: ## Build containers for API testing
docker compose $(af) build
docker compose $(af) -p mdtest up -d
docker run --rm -v .\test\:/test --net md-ship-public ghcr.io/orange-opensource/hurl:latest --test --color --variables-file=/test/api/docker-vars /test/api/customer.hurl
docker compose $(af) -p mdtest down
apitestup: ## Start containers for API testing
docker compose $(af) up -d --remove-orphans
apitestdown: ## Stop containers for API testing
docker compose $(af) down
apitestrun: ## Run Hurl testing scripts in docker container and in mutual network
docker run --rm -v ./test/:/test --net md-ship-public ghcr.io/orange-opensource/hurl:latest --test --color --variables-file=/test/api/docker-vars /test/api/customer.hurl
apitest: ## Build and start docker services and run API testing on them
@make apitestbuild
@make apitestup
@make apitestrun
@make apitestdown

## Local development

Expand Down Expand Up @@ -58,7 +78,11 @@ hurl: ## Run hurl API testing on localhost installation
hurl --variables-file=.\test\api\local-vars .\test\api\customer.hurl

.PHONY: \
api-test \
apitest \
apitestbuild \
apitestdown \
apitestrun \
apitestup \
build \
check-coverage-threshold \
clean \
Expand All @@ -74,4 +98,4 @@ hurl: ## Run hurl API testing on localhost installation
test \
test-with-coverage \
tools \
up \
up \
11 changes: 10 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ Before setting up the project, you need to have a Docker infrastructure installe
First, start the ['Parcel Locker'](https://github.com/magicdelivery/parcel_locker) microservice, followed by the 'Shipping' microservice, as the latter interacts with the former through its network.

```sh
mkdir magicdelivery && cd magicdelivery
git clone https://github.com/magicdelivery/parcel_locker.git
cd parcel_locker
make up
Expand Down Expand Up @@ -39,4 +40,12 @@ Communication with the 'Parcel Locker' microservice is carried out via synchrono

The [testify](https://github.com/stretchr/testify) library is used for unit testing the logic, allowing for the generation of mocks for out-of-process dependencies.

E2E testing of the API is implemented using the [hurl](https://hurl.dev/) command line tool. You can run the tests with the command `make api-test`, which will build and start the necessary containers and then execute the hurl test requests.
E2E testing of the API is implemented using the [hurl](https://hurl.dev/) command line tool. You can run the tests with the command `make testapi`, which will build and start the necessary containers and then execute the hurl test requests.

## Contribution

Since this is a learning project focused on microservices with Golang, I would be incredibly grateful for any advice or ideas for improvement!

## Licence

MIT
2 changes: 1 addition & 1 deletion deploy/docker/compose-api-test.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ services:
extends:
file: compose.yaml
service: ship-redis-populator
entrypoint: ["/redis-populator/populate.sh", "/redis-populator/api-test-data.txt"]
entrypoint: ["sh", "/redis-populator/populate.sh", "/redis-populator/api-test-data.txt"]
depends_on:
- ship-redis-storage

Expand Down
2 changes: 1 addition & 1 deletion deploy/docker/compose.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ services:
container_name: md-ship-redis-populator
depends_on:
- ship-redis-storage
entrypoint: ["/redis-populator/populate.sh", "/redis-populator/data.txt"]
entrypoint: ["bash", "/redis-populator/populate.sh", "/redis-populator/data.txt"]
env_file:
- ./.env
image: redis
Expand Down
24 changes: 13 additions & 11 deletions internal/app/action/deleting.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,17 +9,19 @@ import (
"shipping/internal/domain/repository"
)

func DeleteCustomer(ctx *gin.Context, deleter repository.CustomerDeleter, loader repository.CustomerLoader) {
id := ctx.Params.ByName("id")
if customer, _ := loader.LoadCustomerById(ctx, id); customer == nil {
ctx.JSON(http.StatusNotFound, gin.H{"id": id, "deleted": false, "message": fmt.Sprintf("Customer info not found by id: %s", id)})
return
}
func DeleteCustomer(deleter repository.CustomerDeleter, loader repository.CustomerLoader) gin.HandlerFunc {
return func(ctx *gin.Context) {
id := ctx.Params.ByName("id")
if customer, _ := loader.LoadCustomerById(ctx, id); customer == nil {
ctx.JSON(http.StatusNotFound, gin.H{"id": id, "deleted": false, "message": fmt.Sprintf("Customer info not found by id: %s", id)})
return
}

if err := deleter.DeleteCustomer(ctx, id); err != nil {
ctx.JSON(http.StatusInternalServerError, gin.H{"id": id, "message": err.Error()})
return
}
if err := deleter.DeleteCustomer(ctx, id); err != nil {
ctx.JSON(http.StatusInternalServerError, gin.H{"id": id, "message": err.Error()})
return
}

ctx.JSON(http.StatusOK, gin.H{"id": id, "deleted": true, "message": "Customer info successfully deleted"})
ctx.JSON(http.StatusOK, gin.H{"id": id, "deleted": true, "message": "Customer info successfully deleted"})
}
}
10 changes: 6 additions & 4 deletions internal/app/action/health.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,10 @@ import (
"github.com/gin-gonic/gin"
)

func Ping(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{
"message": "pong",
})
func Ping() gin.HandlerFunc {
return func(ctx *gin.Context) {
ctx.JSON(http.StatusOK, gin.H{
"message": "pong",
})
}
}
75 changes: 40 additions & 35 deletions internal/app/action/loading.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,53 +11,58 @@ import (
"github.com/gin-gonic/gin"
)

func LoadAllCustomers(ctx *gin.Context, loader repository.CustomerLoader) {
if customers, err := loader.LoadAllCustomers(ctx); err != nil {
ctx.JSON(http.StatusInternalServerError, gin.H{"message": err.Error()})
} else {
response := gin.H{"customers": customers}
ctx.JSON(http.StatusOK, response)
func LoadAllCustomers(loader repository.CustomerLoader) gin.HandlerFunc {
return func(ctx *gin.Context) {
if customers, err := loader.LoadAllCustomers(ctx); err != nil {
ctx.JSON(http.StatusInternalServerError, gin.H{"message": err.Error()})
} else {
response := gin.H{"customers": customers}
ctx.JSON(http.StatusOK, response)
}
}
}

func LoadCustomerById(ctx *gin.Context, loader repository.CustomerLoader) {
id := ctx.Params.ByName("id")
customer, err := loader.LoadCustomerById(ctx, id)
if err != nil {
ctx.JSON(http.StatusInternalServerError, gin.H{"id": id, "message": err.Error()})
} else if customer == nil {
ctx.JSON(http.StatusNotFound, gin.H{"id": id, "message": fmt.Sprintf("Customer not found by id: %s", id)})
} else {
ctx.JSON(http.StatusOK, gin.H{"customer": customer})
func LoadCustomerById(loader repository.CustomerLoader) gin.HandlerFunc {
return func(ctx *gin.Context) {
id := ctx.Params.ByName("id")
customer, err := loader.LoadCustomerById(ctx, id)
if err != nil {
ctx.JSON(http.StatusInternalServerError, gin.H{"id": id, "message": err.Error()})
} else if customer == nil {
ctx.JSON(http.StatusNotFound, gin.H{"id": id, "message": fmt.Sprintf("Customer not found by id: %s", id)})
} else {
ctx.JSON(http.StatusOK, gin.H{"customer": customer})
}
}
}

func FindParcelLockersByCustomerId(
ctx *gin.Context,
loader repository.CustomerLoader,
plClient parcel_locker.ParcelLockerClient,
) {
id := ctx.Params.ByName("id")
distanceStr := ctx.DefaultQuery("distance", "10")
distance, err := strconv.ParseFloat(distanceStr, 64)
if err != nil {
ctx.JSON(http.StatusBadRequest, gin.H{"error": "Invalid distance value"})
// c.Abort()
return
}

customer, err := loader.LoadCustomerById(ctx, id)
if err != nil {
ctx.JSON(http.StatusInternalServerError, gin.H{"id": id, "message": err.Error()})
} else if customer == nil {
ctx.JSON(http.StatusNotFound, gin.H{"id": id, "message": fmt.Sprintf("Customer not found by id: %s", id)})
} else {
parcel_lockers, err := plClient.FindParcelLockersNear(ctx, customer, distance)
) gin.HandlerFunc {
return func(ctx *gin.Context) {
id := ctx.Params.ByName("id")
distanceStr := ctx.DefaultQuery("distance", "10")
distance, err := strconv.ParseFloat(distanceStr, 64)
if err != nil {
ctx.JSON(http.StatusInternalServerError, gin.H{"id": id, "message": err.Error()})
ctx.JSON(http.StatusBadRequest, gin.H{"error": "Invalid distance value"})
// c.Abort()
return
}

ctx.JSON(http.StatusOK, gin.H{"parcel_lockers": parcel_lockers})
customer, err := loader.LoadCustomerById(ctx, id)
if err != nil {
ctx.JSON(http.StatusInternalServerError, gin.H{"id": id, "message": err.Error()})
} else if customer == nil {
ctx.JSON(http.StatusNotFound, gin.H{"id": id, "message": fmt.Sprintf("Customer not found by id: %s", id)})
} else {
parcel_lockers, err := plClient.FindParcelLockersNear(ctx, customer, distance)
if err != nil {
ctx.JSON(http.StatusInternalServerError, gin.H{"id": id, "message": err.Error()})
return
}

ctx.JSON(http.StatusOK, gin.H{"parcel_lockers": parcel_lockers})
}
}
}
26 changes: 14 additions & 12 deletions internal/app/action/saving.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,18 +9,20 @@ import (
"github.com/gin-gonic/gin"
)

func SaveCustomer(ctx *gin.Context, saver repository.CustomerSaver) {
var customer model.Customer = model.Customer{}
if err := ctx.BindJSON(&customer); err != nil {
// ctx.BindJSON sets status code 400, thus the next code definition does not effect
ctx.JSON(http.StatusInternalServerError, map[string]any{"customer": customer, "created": false, "message": err.Error()})
return
}
func SaveCustomer(saver repository.CustomerSaver) gin.HandlerFunc {
return func(ctx *gin.Context) {
var customer model.Customer = model.Customer{}
if err := ctx.BindJSON(&customer); err != nil {
// ctx.BindJSON sets status code 400, thus the next code definition does not effect
ctx.JSON(http.StatusInternalServerError, map[string]any{"customer": customer, "created": false, "message": err.Error()})
return
}

if err := saver.SaveCustomer(ctx, customer); err != nil {
ctx.JSON(http.StatusInternalServerError, map[string]any{"customer": customer, "created": false, "message": err.Error()})
return
}
if err := saver.SaveCustomer(ctx, customer); err != nil {
ctx.JSON(http.StatusInternalServerError, map[string]any{"customer": customer, "created": false, "message": err.Error()})
return
}

ctx.JSON(http.StatusCreated, gin.H{"customer": customer, "created": true, "message": "Customer info created successfully"})
ctx.JSON(http.StatusCreated, gin.H{"customer": customer, "created": true, "message": "Customer info created successfully"})
}
}
12 changes: 6 additions & 6 deletions internal/app/route/router.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,11 @@ func SetupRouter(config *config.Config) *gin.Engine {
loader, saver, deleter := storage.DefaultServices(&config.RedisStorage)
plClient := parcel_locker.NewParcelLockerClient(config)

r.GET("/ping", func(c *gin.Context) { action.Ping(c) })
r.GET("/customers", func(c *gin.Context) { action.LoadAllCustomers(c, loader) })
r.GET("/customer/:id", func(c *gin.Context) { action.LoadCustomerById(c, loader) })
r.GET("/customer-parcel-lockers/:id", func(c *gin.Context) { action.FindParcelLockersByCustomerId(c, loader, plClient) })
r.POST("/customer", func(c *gin.Context) { action.SaveCustomer(c, saver) })
r.DELETE("/customer/:id", func(c *gin.Context) { action.DeleteCustomer(c, deleter, loader) })
r.GET("/ping", action.Ping())
r.GET("/customers", action.LoadAllCustomers(loader))
r.GET("/customer/:id", action.LoadCustomerById(loader))
r.GET("/customer-parcel-lockers/:id", action.FindParcelLockersByCustomerId(loader, plClient))
r.POST("/customer", action.SaveCustomer(saver))
r.DELETE("/customer/:id", action.DeleteCustomer(deleter, loader))
return r
}

0 comments on commit 8939487

Please sign in to comment.