├── go.mod ├── .golangci.yml ├── .github └── workflows │ ├── codecov.yml │ ├── golangci-lint.yml │ └── test.yml ├── go.sum ├── LICENSE ├── organism_test.go ├── organism.go └── README.md /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/citilinkru/organism 2 | 3 | go 1.13 4 | 5 | require github.com/stretchr/testify v1.7.0 6 | -------------------------------------------------------------------------------- /.golangci.yml: -------------------------------------------------------------------------------- 1 | run: 2 | timeout: 5m 3 | modules-download-mode: vendor 4 | skip-files: 5 | - '.*_test.go' 6 | 7 | linters: 8 | enable: 9 | - prealloc 10 | - dogsled 11 | - exportloopref 12 | - unconvert 13 | - unparam 14 | - whitespace 15 | - bodyclose 16 | - gosec 17 | - asciicheck 18 | - depguard 19 | - errorlint 20 | - goconst 21 | - gocritic 22 | -------------------------------------------------------------------------------- /.github/workflows/codecov.yml: -------------------------------------------------------------------------------- 1 | name: Test and coverage 2 | on: [push, pull_request] 3 | jobs: 4 | build: 5 | runs-on: ubuntu-latest 6 | steps: 7 | - uses: actions/checkout@v2 8 | with: 9 | fetch-depth: 2 10 | - uses: actions/setup-go@v2 11 | with: 12 | go-version: '1.13' 13 | - name: Run coverage 14 | run: go test -race -coverprofile=coverage.txt -covermode=atomic 15 | - name: Upload coverage to Codecov 16 | run: bash <(curl -s https://codecov.io/bash) -------------------------------------------------------------------------------- /.github/workflows/golangci-lint.yml: -------------------------------------------------------------------------------- 1 | name: golangci-lint 2 | on: 3 | push: 4 | pull_request: 5 | workflow_dispatch: 6 | jobs: 7 | golangci: 8 | strategy: 9 | matrix: 10 | go-version: [1.13, 1.14, 1.15, 1.16] 11 | name: lint 12 | runs-on: ubuntu-latest 13 | steps: 14 | - uses: actions/checkout@v2 15 | - uses: actions/setup-go@v2 16 | with: 17 | go-version: ${{ matrix.go-version }} 18 | - run: go mod vendor 19 | - name: golangci-lint 20 | uses: golangci/golangci-lint-action@v2 21 | with: 22 | version: v1.40 23 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: test 2 | on: 3 | push: 4 | pull_request: 5 | workflow_dispatch: 6 | jobs: 7 | test: 8 | strategy: 9 | matrix: 10 | go-version: [1.13, 1.14, 1.15, 1.16] 11 | name: test 12 | runs-on: ubuntu-latest 13 | steps: 14 | - name: checkout 15 | uses: actions/checkout@v2 16 | - uses: actions/setup-go@v2 17 | with: 18 | go-version: ${{ matrix.go-version }} 19 | - name: run tests 20 | run: go test -json ./... > test.json 21 | - name: Annotate tests 22 | if: always() 23 | uses: guyarb/golang-test-annotations@v0.3.0 24 | with: 25 | test-results: test.json -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= 2 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 3 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 4 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 5 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 6 | github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= 7 | github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 8 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= 9 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 10 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= 11 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 12 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Citilink LLC 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /organism_test.go: -------------------------------------------------------------------------------- 1 | package organism 2 | 3 | import ( 4 | "github.com/stretchr/testify/assert" 5 | "testing" 6 | ) 7 | 8 | func TestNewOrganism(t *testing.T) { 9 | o := New() 10 | assert.True(t, o.IsAlive()) 11 | assert.False(t, o.IsReady()) 12 | 13 | o.Ready() 14 | 15 | assert.True(t, o.IsReady()) 16 | 17 | o.Die() 18 | 19 | assert.False(t, o.IsAlive()) 20 | assert.True(t, o.IsReady()) 21 | } 22 | 23 | func TestOrganism_GrowLimb_One(t *testing.T) { 24 | o := New() 25 | limb := o.GrowLimb() 26 | 27 | assert.True(t, limb.IsAlive()) 28 | assert.False(t, limb.IsReady()) 29 | 30 | limb.Ready() 31 | 32 | assert.True(t, limb.IsAlive()) 33 | assert.True(t, limb.IsReady()) 34 | assert.True(t, o.IsAlive()) 35 | assert.False(t, o.IsReady()) 36 | 37 | o.Ready() 38 | 39 | assert.True(t, limb.IsAlive()) 40 | assert.True(t, limb.IsReady()) 41 | assert.True(t, o.IsAlive()) 42 | assert.True(t, o.IsReady()) 43 | 44 | limb.Die() 45 | 46 | assert.False(t, limb.IsAlive()) 47 | assert.True(t, limb.IsReady()) 48 | assert.False(t, o.IsAlive()) 49 | assert.True(t, o.IsReady()) 50 | 51 | o.Die() 52 | 53 | assert.False(t, limb.IsAlive()) 54 | assert.True(t, limb.IsReady()) 55 | assert.False(t, o.IsAlive()) 56 | assert.True(t, o.IsReady()) 57 | } 58 | -------------------------------------------------------------------------------- /organism.go: -------------------------------------------------------------------------------- 1 | package organism 2 | 3 | // New create new organism 4 | func New() *Organism { 5 | o := &Organism{} 6 | o.core = o.GrowLimb() 7 | return o 8 | } 9 | 10 | // Organism is your application, it has core and limbs. Each Limb describes important part of your app, and if this part 11 | // is dead, then organism is partially dead too (liveness probe). If each Limb of Organism, and the core too, are ready 12 | // to work, then whole Organism ready too work too (readiness probe) 13 | type Organism struct { 14 | limbs []*Limb 15 | core *Limb 16 | } 17 | 18 | // GrowLimb grow new Limb for Organism and returns it 19 | func (o *Organism) GrowLimb() *Limb { 20 | limb := newLimb() 21 | o.limbs = append(o.limbs, limb) 22 | 23 | return limb 24 | } 25 | 26 | func (o *Organism) IsReady() bool { 27 | for _, l := range o.limbs { 28 | if !l.IsReady() { 29 | return false 30 | } 31 | } 32 | 33 | return true 34 | } 35 | 36 | func (o *Organism) IsAlive() bool { 37 | for _, l := range o.limbs { 38 | if !l.IsAlive() { 39 | return false 40 | } 41 | } 42 | 43 | return true 44 | } 45 | 46 | // Ready marks core of Organism as ready 47 | func (o *Organism) Ready() { 48 | o.core.Ready() 49 | } 50 | 51 | // Ready marks core of Organism as dead 52 | func (o *Organism) Die() { 53 | o.core.Die() 54 | } 55 | 56 | func newLimb() *Limb { 57 | return &Limb{isAlive: true} 58 | } 59 | 60 | type Limb struct { 61 | isReady bool 62 | isAlive bool 63 | } 64 | 65 | // Ready marks Limb as ready 66 | func (l *Limb) Ready() { 67 | l.isReady = true 68 | } 69 | 70 | // Die marks Limb as dead (not alive) 71 | func (l *Limb) Die() { 72 | l.isAlive = false 73 | } 74 | 75 | func (l *Limb) IsReady() bool { 76 | return l.isReady 77 | } 78 | 79 | func (l *Limb) IsAlive() bool { 80 | return l.isAlive 81 | } 82 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Organism 2 | 3 | ![Lint](https://github.com/citilinkru/organism/actions/workflows/golangci-lint.yml/badge.svg?branch=master) 4 | ![Tests](https://github.com/citilinkru/organism/actions/workflows/test.yml/badge.svg?branch=master) 5 | [![codecov](https://codecov.io/gh/citilinkru/organism/branch/master/graph/badge.svg)](https://codecov.io/gh/citilinkru/organism) 6 | [![Go Report Card](https://goreportcard.com/badge/github.com/citilinkru/organism)](https://goreportcard.com/report/github.com/citilinkru/organism) 7 | [![License](https://img.shields.io/github/license/mashape/apistatus.svg)](https://github.com/citilinkru/organism/blob/master/LICENSE) 8 | [![GoDoc](https://godoc.org/github.com/citilinkru/organism?status.svg)](https://godoc.org/github.com/citilinkru/organism) 9 | [![Release](https://img.shields.io/github/release/citilinkru/organism.svg?style=flat-square)](https://github.com/citilinkru/organism/releases/latest) 10 | 11 | Abstraction for liveness and readiness probes of your app 12 | 13 | # Description 14 | We can describe each app as Organism, that consists of core and Limbs. Each Limb describes important part of your app, 15 | without which app can't work properly. If each Limb is ready to work, then the whole organism ready to work too 16 | (readiness probe). If at least one Limb is dead, then the whole organism is partially dead too. 17 | 18 | # Example 19 | Let's take simple http handlers funcs, to answer on readiness and liveness probes 20 | 21 | ```go 22 | package main 23 | 24 | import ( 25 | "github.com/citilinkru/organism" 26 | "github.com/gorilla/mux" 27 | "net/http" 28 | "log" 29 | ) 30 | 31 | func main() { 32 | o := organism.New() 33 | limb1 := o.GrowLimb() 34 | go func() { 35 | defer limb1.Die() 36 | limb1.Ready() 37 | err := DoSmtValuable() 38 | if err != nil { 39 | log.Println("something wrong with 1: ", err) 40 | } 41 | }() 42 | 43 | limb2 := o.GrowLimb() 44 | go func() { 45 | defer limb2.Die() 46 | limb2.Ready() 47 | err := DoAnotherValuable() 48 | if err != nil { 49 | log.Println("something wrong with 2: ", err) 50 | } 51 | }() 52 | 53 | o.Ready() 54 | // ... 55 | 56 | r := mux.NewRouter() 57 | r.HandleFunc("/healty", ReadinessHandler(o)) 58 | r.HandleFunc("/healtz", LivenessHandler(o)) 59 | http.Handle("/", r) 60 | 61 | // ... 62 | } 63 | 64 | func LivenessHandler(o *organism.Organism) http.HandlerFunc { 65 | return func(writer http.ResponseWriter, request *http.Request) { 66 | if !o.IsAlive() { 67 | return 68 | } 69 | 70 | _, err := writer.Write([]byte("OK")) 71 | if err != nil { 72 | writer.WriteHeader(http.StatusInternalServerError) 73 | } 74 | } 75 | } 76 | 77 | func ReadinessHandler(o *organism.Organism) http.HandlerFunc { 78 | return func(writer http.ResponseWriter, request *http.Request) { 79 | if !o.IsReady() { 80 | return 81 | } 82 | 83 | _, err := writer.Write([]byte("OK")) 84 | if err != nil { 85 | writer.WriteHeader(http.StatusInternalServerError) 86 | } 87 | } 88 | } 89 | ``` 90 | 91 | Testing 92 | ----------- 93 | Unit-tests: 94 | ```bash 95 | go test -v -race ./... 96 | ``` 97 | 98 | Run linter: 99 | ```bash 100 | go mod vendor \ 101 | && docker run --rm -v $(pwd):/app -w /app golangci/golangci-lint:v1.40 golangci-lint run -v \ 102 | && rm -R vendor 103 | ``` 104 | 105 | CONTRIBUTE 106 | ----------- 107 | * write code 108 | * run `go fmt ./...` 109 | * run all linters and tests (see above) 110 | * create a PR describing the changes 111 | 112 | LICENSE 113 | ----------- 114 | MIT 115 | 116 | AUTHOR 117 | ----------- 118 | Nikita Sapogov --------------------------------------------------------------------------------