Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Add health check service and http handler #17

Merged
merged 1 commit into from Mar 8, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
4 changes: 2 additions & 2 deletions Makefile
Expand Up @@ -3,7 +3,7 @@ PLATFORM := $(shell uname | tr A-Z a-z)
lint:
if [ ! -f ./bin/golangci-lint ] ; \
then \
curl -sfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s v1.37.1; \
curl -sfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s v1.38.0; \
fi;
./bin/golangci-lint run

Expand All @@ -26,4 +26,4 @@ db-migrate: download-golang-migrate-binary
db-test-migrate: download-golang-migrate-binary
./migrate.$(PLATFORM)-amd64 -source file://db/migrations -database ${POSTMAND_TEST_DATABASE_URL} up

.PHONY: lint test download-golang-migrate-binary db-migrate db-test-migrate
.PHONY: lint test mock download-golang-migrate-binary db-migrate db-test-migrate
21 changes: 21 additions & 0 deletions cmd/postmand/main.go
Expand Up @@ -17,6 +17,16 @@ import (
"go.uber.org/zap"
)

func healthcheckServer(db *sqlx.DB, logger *zap.Logger) {
pingRepository := repository.NewPing(db)
pingService := service.NewPing(pingRepository)
pingHandler := handler.NewPing(pingService, logger)
mux := http.NewRouter(logger)
mux.Get("/healthz", pingHandler.Healthz)
server := http.NewServer(mux, env.GetInt("POSTMAND_HEALTH_CHECK_HTTP_PORT", 8000), logger)
server.Run()
}

func main() {
// Setup logger
logger, err := zap.NewProduction()
Expand Down Expand Up @@ -65,6 +75,9 @@ func main() {
Aliases: []string{"w"},
Usage: "executes worker to dispatch webhooks",
Action: func(c *cli.Context) error {
// Start health check
go healthcheckServer(db, logger)

deliveryRepository := repository.NewDelivery(db)
pollingInterval := time.Duration(env.GetInt("POSTMAND_POLLING_INTERVAL", 1000)) * time.Millisecond
workerService := service.NewWorker(deliveryRepository, logger, pollingInterval)
Expand All @@ -77,12 +90,20 @@ func main() {
Aliases: []string{"s"},
Usage: "executes http server",
Action: func(c *cli.Context) error {
// Start health check
go healthcheckServer(db, logger)

// Create repositories
webhookRepository := repository.NewWebhook(db)
deliveryRepository := repository.NewDelivery(db)
deliveryAttemptRepository := repository.NewDeliveryAttempt(db)

// Create services
webhookService := service.NewWebhook(webhookRepository)
deliveryService := service.NewDelivery(deliveryRepository)
deliveryAttemptService := service.NewDeliveryAttempt(deliveryAttemptRepository)

// Create http handlers
webhookHandler := handler.NewWebhook(webhookService, logger)
deliveryHandler := handler.NewDelivery(deliveryService, logger)
deliveryAttemptHandler := handler.NewDeliveryAttempt(deliveryAttemptService, logger)
Expand Down
2 changes: 1 addition & 1 deletion go.mod
Expand Up @@ -13,7 +13,7 @@ require (
github.com/jmoiron/sqlx v1.3.1
github.com/joho/godotenv v1.3.0
github.com/jpillora/backoff v1.0.0
github.com/lib/pq v1.9.0
github.com/lib/pq v1.10.0
github.com/steinfletcher/apitest v1.5.2
github.com/stretchr/testify v1.7.0
github.com/urfave/cli/v2 v2.3.0
Expand Down
4 changes: 2 additions & 2 deletions go.sum
Expand Up @@ -241,8 +241,8 @@ github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
github.com/lib/pq v1.1.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
github.com/lib/pq v1.8.0/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
github.com/lib/pq v1.9.0 h1:L8nSXQQzAYByakOFMTwpjRoHsMJklur4Gi59b6VivR8=
github.com/lib/pq v1.9.0/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
github.com/lib/pq v1.10.0 h1:Zx5DJFEYQXio93kgXnQ09fXNiUKsqv4OUEu2UtGcB1E=
github.com/lib/pq v1.10.0/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
github.com/markbates/pkger v0.15.1/go.mod h1:0JoVlrol20BSywW79rN3kdFFsE5xYM+rSCQDXbLhiuI=
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ=
Expand Down
45 changes: 45 additions & 0 deletions http/handler/ping.go
@@ -0,0 +1,45 @@
package handler

import (
"net/http"

"github.com/allisson/postmand"
"go.uber.org/zap"
)

type pingResponse struct {
Success bool `json:"success"`
}

// Ping implements interface for health check.
type Ping struct {
pingService postmand.PingService
logger *zap.Logger
}

// Healthz returns health check response.
func (p Ping) Healthz(w http.ResponseWriter, r *http.Request) {
pr := pingResponse{}

if err := p.pingService.Run(r.Context()); err != nil {
p.logger.Error(
"service-error",
zap.String("name", "PingService"),
zap.String("method", "Run"),
zap.Error(err),
)
makeJSONResponse(w, http.StatusInternalServerError, &pr, p.logger)
return
}

pr.Success = true
makeJSONResponse(w, http.StatusOK, &pr, p.logger)
}

// NewPing creates a new Ping.
func NewPing(pingService postmand.PingService, logger *zap.Logger) *Ping {
return &Ping{
pingService: pingService,
logger: logger,
}
}
53 changes: 53 additions & 0 deletions http/handler/ping_test.go
@@ -0,0 +1,53 @@
package handler

import (
"errors"
nethttp "net/http"
"testing"

"github.com/allisson/postmand/http"
"github.com/allisson/postmand/mocks"
"github.com/steinfletcher/apitest"
"github.com/stretchr/testify/mock"
"go.uber.org/zap"
)

func TestPing(t *testing.T) {
logger, _ := zap.NewDevelopment()

t.Run("With success", func(t *testing.T) {
pingService := &mocks.PingService{}
pingHandler := NewPing(pingService, logger)
router := http.NewRouter(logger)
router.Get("/healthz", pingHandler.Healthz)

pingService.On("Run", mock.Anything).Return(nil)
apitest.New().
Handler(router).
Get("/healthz").
Expect(t).
Body(`{"success":true}`).
Status(nethttp.StatusOK).
End()

pingService.AssertExpectations(t)
})

t.Run("With error", func(t *testing.T) {
pingService := &mocks.PingService{}
pingHandler := NewPing(pingService, logger)
router := http.NewRouter(logger)
router.Get("/healthz", pingHandler.Healthz)

pingService.On("Run", mock.Anything).Return(errors.New("BOOM"))
apitest.New().
Handler(router).
Get("/healthz").
Expect(t).
Body(`{"success":false}`).
Status(nethttp.StatusInternalServerError).
End()

pingService.AssertExpectations(t)
})
}
1 change: 1 addition & 0 deletions local.env
Expand Up @@ -4,3 +4,4 @@ POSTMAND_DATABASE_MIGRATION_DIR='file://db/migrations'
POSTMAND_DATABASE_MAX_OPEN_CONNS='2'
POSTMAND_POLLING_INTERVAL='1000'
POSTMAND_HTTP_PORT='8000'
POSTMAND_HEALTH_CHECK_HTTP_PORT='8001'
28 changes: 28 additions & 0 deletions mocks/PingRepository.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

28 changes: 28 additions & 0 deletions mocks/PingService.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions repository.go
Expand Up @@ -46,3 +46,8 @@ type DeliveryAttemptRepository interface {
type MigrationRepository interface {
Run(ctx context.Context) error
}

// PingRepository is the interface that will be used to run ping against database.
type PingRepository interface {
Run(ctx context.Context) error
}
22 changes: 22 additions & 0 deletions repository/ping.go
@@ -0,0 +1,22 @@
package repository

import (
"context"

"github.com/jmoiron/sqlx"
)

// Ping implements postmand.PingRepository interface.
type Ping struct {
db *sqlx.DB
}

// Run ping operation against the database.
func (p Ping) Run(ctx context.Context) error {
return p.db.PingContext(ctx)
}

// NewPing will create an implementation of postmand.PingRepository.
func NewPing(db *sqlx.DB) *Ping {
return &Ping{db: db}
}
17 changes: 17 additions & 0 deletions repository/ping_test.go
@@ -0,0 +1,17 @@
package repository

import (
"context"
"testing"

"github.com/stretchr/testify/assert"
)

func TestPing(t *testing.T) {
ctx := context.Background()
th := newTestHelper()
defer th.db.Close()

err := th.pingRepository.Run(ctx)
assert.Nil(t, err)
}
2 changes: 2 additions & 0 deletions repository/util_test.go
Expand Up @@ -21,6 +21,7 @@ type testHelper struct {
webhookRepository *Webhook
deliveryRepository *Delivery
deliveryAttemptRepository *DeliveryAttempt
pingRepository *Ping
}

func newTestHelper() testHelper {
Expand All @@ -31,5 +32,6 @@ func newTestHelper() testHelper {
webhookRepository: NewWebhook(db),
deliveryRepository: NewDelivery(db),
deliveryAttemptRepository: NewDeliveryAttempt(db),
pingRepository: NewPing(db),
}
}
5 changes: 5 additions & 0 deletions service.go
Expand Up @@ -36,3 +36,8 @@ type DeliveryAttemptService interface {
Get(ctx context.Context, getOptions RepositoryGetOptions) (*DeliveryAttempt, error)
List(ctx context.Context, listOptions RepositoryListOptions) ([]*DeliveryAttempt, error)
}

// PingService is the interface that will be used to perform ping operation against database.
type PingService interface {
Run(ctx context.Context) error
}
22 changes: 22 additions & 0 deletions service/ping.go
@@ -0,0 +1,22 @@
package service

import (
"context"

"github.com/allisson/postmand"
)

// Ping implements postmand.PingService interface.
type Ping struct {
pingRepository postmand.PingRepository
}

// Run ping operation against the database.
func (p Ping) Run(ctx context.Context) error {
return p.pingRepository.Run(ctx)
}

// NewPing will create an implementation of postmand.PingService.
func NewPing(pingRepository postmand.PingRepository) *Ping {
return &Ping{pingRepository: pingRepository}
}
20 changes: 20 additions & 0 deletions service/ping_test.go
@@ -0,0 +1,20 @@
package service

import (
"context"
"testing"

"github.com/allisson/postmand/mocks"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
)

func TestPing(t *testing.T) {
pingRepository := &mocks.PingRepository{}
pingService := NewPing(pingRepository)
pingRepository.On("Run", mock.Anything).Return(nil)
ctx := context.Background()
err := pingService.Run(ctx)
assert.Nil(t, err)
pingRepository.AssertExpectations(t)
}