Skip to content

Commit

Permalink
feat: flipt feature added for feature enable and disable (#227)
Browse files Browse the repository at this point in the history
* feat: flipt feature added for feature enable and disable

* modify flow

* fix : rename readme.md to README.md and add flipt details to main README.md

* added flipt.yml and modified logger comment
  • Loading branch information
NiravThumar authored Jul 24, 2024
1 parent 1be46ce commit 6ceca16
Show file tree
Hide file tree
Showing 13 changed files with 380 additions and 13 deletions.
4 changes: 4 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -75,3 +75,7 @@ MQ_DB_QUERYSTRING=sslmode=disable
# PROJECT_ID=
# SUBSCRIPTION_ID=

# flipt Config
FLIPT_ENABLED=true
FLIPT_HOST=localhost
FLIPT_PORT=9000
29 changes: 21 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ for ex: `start` commands run `go run app.go api` if your app.go is somewhere els

- **make install app_name={BINARY_NAME}**: It will generate optimized binary with `-s` and `-w` ldflags.

- **make test** : To run testcase for entire project, `.env.testing` env need to use while writing testcase, you need to load
- **make test** : To run testcase for entire project, `.env.testing` env need to use while writing testcase, you need to load

- **make test-wo-cache**: To run testcases with without cache.

Expand All @@ -86,10 +86,11 @@ Ory Kratos doesn't provide UI, You have to specify the endpoints for different U
**Note:** ory Kratos is an optional integration to the boilerplate, if you want to use it you need to follow the below steps.

- Inside the ```.env``` you'll have to set the ```KRATOS_ENABLED``` for enabling the kratos integration.
- According to your config requirements, you will need to change the corresponding files inside the ```/pkg/kratos``` folder.
- According to your config requirements, you will need to change the corresponding files inside the ```/pkg/kratos``` folder.
- Then after that for all the endpoints you want Kratos authentication you'll have to add ```middlewares.Authenticated```. After that, you can add your own handle and write business logic over there using user details.
- For more details, you can see the [documentation](./pkg/kratos/readme.md) section.


## Execution

1. Run ```docker-compose up``` to spin up the database and admire. In the case of a Kratos Enabled user this command ```docker-compose --profile kratos up```.
Expand All @@ -105,17 +106,29 @@ Ory Kratos doesn't provide UI, You have to specify the endpoints for different U
```
**Note:** Use this for local development environment.


## Flipt Integration
Flipt is an open-source feature flag management tool that allows you to enable or disable features in your application without deploying new code. It provides a simple way to control the availability of features to your users, perform A/B testing, and gradually roll out new features.

**Note:** Flipt is an optional integration to the boilerplate, if you want to use it you need to follow the below steps.

1. Set the `FLIPT_ENABLED` environment variable to `true` to enable Flipt support.
2. Set the `FLIPT_HOST` (e.g., `localhost`) and `FLIPT_PORT` (e.g., `9000`) environment variables to communicate with the Flipt backend.
3. For setting up the ```Flipt``` service with other services, you have to execute ```docker-compose --profile flipt up```.
4. Now you can access flipt UI on [http://localhost:8081](http://localhost:8081)
- For more details, you can see the [documentation](./helpers/flipt/readme.md) section.

### **Migrations**
- **CREATE :** To create migrations i have discribed details in [Kick Start Commands](#kick-start-commands) section.
- **RUN :** To run migration there is two command `make migration-up` && `make migration-down`.
- Migration needs `-- +migrate Up` and `-- +migrate Down` respectively in starting of files, this is required because we are using [sql-migrate](https://github.com/rubenv/sql-migrate) package
- Migration needs `-- +migrate Up` and `-- +migrate Down` respectively in starting of files, this is required because we are using [sql-migrate](https://github.com/rubenv/sql-migrate) package

### **Messaging Queue**
- We are using [watermill](https://watermill.io/) package for messaging queue.
- Watermill is a Golang library for working efficiently with message streams. It is intended for building event-driven applications.


- #### Multiple Message Queue Broker Support
- #### Multiple Message Queue Broker Support
- We are supporting 5 types of message queue broker at this time `rabbitmq`, `redis`, `googleCloud`,`sql(postgres,mysql)` & `kafka`
- It allows us to switch to message queue broker without changing too much stuff.
- Watermill package allows us to do that.
Expand Down Expand Up @@ -152,7 +165,7 @@ Ory Kratos doesn't provide UI, You have to specify the endpoints for different U
```
- #### Command to run worker
```go
go run app.go worker --retry-delay 400 --retry-count 3 --topic user
go run app.go worker --retry-delay 400 --retry-count 3 --topic user
// --retry-delay 400 --retry-count 3 are optional
// --retry-delay 400 means it will retry after 400ms
// --retry-count 3 means it will retry 3 times
Expand All @@ -178,12 +191,12 @@ Ory Kratos doesn't provide UI, You have to specify the endpoints for different U
- #### Dead Letter Queue
- The `dead letter queue`, also known as the `poison queue` in watermill, is a designated destination for messages that have failed to undergo processing by a consumer.
- The name of this queue is specified in the `DEAD_LETTER_QUEUE` environment variable, we are storing failed job into database.
- Command to run dead letter queue
- Command to run dead letter queue
```go
go run app.go dead-letter-queue
```
---
---
### **Code Walk-through**
- #### Config:
- We are using [envconfig](https://github.com/kelseyhightower/envconfig) which binds env values to struct elements.
Expand Down Expand Up @@ -254,7 +267,7 @@ Ory Kratos doesn't provide UI, You have to specify the endpoints for different U
- `json` - in struct represent key name when it will be returned in json format.
- `db` - represents name of database column, it's supported by `goqu` package.
- `validate` - if you want to validate fields that are required you can set `validate:"required"`
- `omitempty` - this will be used with along side of json, if value will be empty then that element will be trimmed from response of json.
- `omitempty` - this will be used with along side of json, if value will be empty then that element will be trimmed from response of json.
- #### Controller:
- As descirbed in [route](#route) controller init method should be call in route.
- Each controller must have their struct which contains model object, that will be use to call function of models.
Expand Down
14 changes: 13 additions & 1 deletion cli/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (

"github.com/Improwised/golang-api/config"
"github.com/Improwised/golang-api/database"
helpers "github.com/Improwised/golang-api/helpers/flipt"
"github.com/Improwised/golang-api/pkg/events"
"github.com/Improwised/golang-api/pkg/watermill"
"github.com/Improwised/golang-api/routes"
Expand Down Expand Up @@ -44,12 +45,23 @@ func GetAPICommandDef(cfg config.AppConfig, logger *zap.Logger) cobra.Command {
return err
}

pub, err := watermill.InitPublisher(cfg,false)
pub, err := watermill.InitPublisher(cfg, false)
if err != nil {
return err
}

// Initialize Flipt client for flipt functionality
err = helpers.InitFliptClient()
if err != nil {
logger.Error("Error while initialize client", zap.Error(err))
if err.Error() != "flipt is not enabled" {
return err
}
}

// setup routes
err = routes.Setup(app, db, logger, cfg, events, promMetrics, pub)

if err != nil {
return err
}
Expand Down
8 changes: 8 additions & 0 deletions config/flipt.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package config

// Flipt Config
type FliptConfig struct {
Host string `envconfig:"FLIPT_HOST"`
Port string `envconfig:"FLIPT_PORT"`
Enabled bool `envconfig:"FLIPT_ENABLEd"`
}
1 change: 1 addition & 0 deletions config/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ type AppConfig struct {
DB DBConfig
Kratos KratosConfig
MQ MQConfig
Flipt FliptConfig
}

// GetConfig Collects all configs
Expand Down
1 change: 0 additions & 1 deletion controllers/api/v1/health_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,6 @@ func (hc *HealthController) Db(ctx *fiber.Ctx) error {
hc.logger.Error("error while health checking of db", zap.Error(err))
return utils.JSONError(ctx, http.StatusInternalServerError, constants.ErrHealthCheckDb)
}

return utils.JSONSuccess(ctx, http.StatusOK, "ok")
}

Expand Down
27 changes: 27 additions & 0 deletions docker-compose.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -55,5 +55,32 @@ services:
volumes:
- "./pkg/kratos:/etc/config/kratos"

init:
image: flipt/flipt:latest
profiles: [flipt]
command: ["./flipt", "import", "flipt.yml"]
environment:
- FLIPT_LOG_LEVEL=debug
- FLIPT_META_TELEMETRY_ENABLED=false
volumes:
- "./helpers/flipt/flipt.yml:/flipt.yml"
- "flipt_data:/var/opt/flipt"

flipt:
image: flipt/flipt:latest
profiles: [flipt]
command: ["./flipt"]
depends_on:
- init
ports:
- "8081:8080"
- "9000:9000"
environment:
- FLIPT_LOG_LEVEL=debug
- FLIPT_META_TELEMETRY_ENABLED=false
volumes:
- "flipt_data:/var/opt/flipt"

volumes:
pgdata:
flipt_data:
3 changes: 2 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,10 @@ require (
github.com/sirupsen/logrus v1.9.3
github.com/spf13/cobra v1.7.0
github.com/stretchr/testify v1.8.4
go.flipt.io/flipt-grpc v1.11.0
go.uber.org/zap v1.24.0
golang.org/x/crypto v0.18.0
google.golang.org/grpc v1.60.1
gopkg.in/go-playground/validator.v9 v9.31.0
)

Expand Down Expand Up @@ -68,7 +70,6 @@ require (
google.golang.org/genproto v0.0.0-20240116215550-a9fa1716bcac // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20240122161410-6c6643bf1457 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20240116215550-a9fa1716bcac // indirect
google.golang.org/grpc v1.60.1 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
)

Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -459,6 +459,8 @@ github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d/go.mod h1:rHwXgn7Jul
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
go.einride.tech/aip v0.65.0 h1:aqKEV1g9diXcR6DAxBVZoJn6ho8SuC+TOZFXzuu7kLU=
go.einride.tech/aip v0.65.0/go.mod h1:wcRZ57XFEvERWLPy9VqDBtXc/ZFj7ugsd32F5o8Th+s=
go.flipt.io/flipt-grpc v1.11.0 h1:EHtZfelq7lk2nqW8Rx+bKLM+4DShx144aAhFCMj/9Ac=
go.flipt.io/flipt-grpc v1.11.0/go.mod h1:fwyou/igTEr+pKD26XFO1s6aQx4MNbvsKUoaALvQPIw=
go.mongodb.org/mongo-driver v1.7.3/go.mod h1:NqaYOwnXWr5Pm7AOpO5QFxKJ503nbMse/R79oO62zWg=
go.mongodb.org/mongo-driver v1.7.5/go.mod h1:VXEWRZ6URJIkUq2SCAyapmhH0ZLRBP+FT4xhp5Zvxng=
go.mongodb.org/mongo-driver v1.10.0/go.mod h1:wsihk0Kdgv8Kqu1Anit4sfK+22vSFbUrAVEYRhCXrA8=
Expand Down
140 changes: 140 additions & 0 deletions helpers/flipt/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
# Golang Boilerplate with Flipt Integration

This project integrates [Flipt](https://flipt.io) for feature flag management.

## What is Flipt?

Flipt is an open-source feature flag management tool that allows you to enable or disable features in your application without deploying new code. It provides a simple way to control the availability of features to your users, perform A/B testing, and gradually roll out new features.

## Why Use Flipt?

- **Feature Management**: Easily turn features on or off based on various conditions.
- **A/B Testing**: Run experiments to determine which features perform better.
- **Gradual Rollout**: Gradually release features to a subset of users to minimize risk.
- **Decoupled Releases**: Deploy code without enabling the feature, reducing the risk of breaking changes.

## Configuration

1. Set the `FLIPT_ENABLED` environment variable to `true` to enable Flipt support.
2. Set the `FLIPT_HOST` (e.g., `localhost`) and `FLIPT_PORT` (e.g., `9000`) environment variables to communicate with the Flipt backend.
3. For setting up the ```Flipt``` service with other services, you have to execute ```docker-compose --profile flipt up```.
4. Now you can access flipt UI on [http://localhost:8081](http://localhost:8081)

## Changes Made to Integrate Flipt

1. **Initialize Flipt Client on Application Startup:**

In `api.go`, add the following code to initialize the Flipt client:

```go
// Initialize Flipt client for flipt functionality
err = helpers.InitFliptClient()
if err != nil {
logger.Error("Error while initializing client", zap.Error(err))
if err.Error() != "flipt is not enabled" {
return err
}
}
```

2. **Helper Functions to Communicate with Flipt Backend:**

The `helpers` package includes two methods for interacting with Flipt:

- **GetBooleanFlag**: Retrieves a boolean flag based on the flag key.

```go
type BooleanFlagResponse struct {
Key string `json:"key"`
Name string `json:"name"`
Description string `json:"description"`
Enabled bool `json:"enabled,omitempty"`
}
func GetBooleanFlag(flagKey string) (BooleanFlagResponse, error) {
// Implementation here
}
```

- **GetVariantFlag**: Retrieves a variant flag based on the flag key, entity ID, and context map.

```go
type Context struct {
Key string `json:"key"`
Value string `json:"value"`
}
type VariantFlagResponse struct {
RequestContext Context `json:"request_context"`
Match bool `json:"match,omitempty"`
FlagKey string `json:"flag_key"`
SegmentKey string `json:"segment_key,omitempty"`
Value string `json:"value,omitempty"`
}
func GetVariantFlag(flagKey string, entityId string, contextMap map[string]string) (VariantFlagResponse, error) {
// Implementation here
}
```

## How to Use Flipt in Your Code

### Use Boolean Flag

1. Open [Flipt Flags](http://localhost:8081/#/flags) and create a new flag:
- Enter the name and key for the flag (e.g., `advertisement`).
- Set the type to boolean, add a description, enable the flag, and click create.

2. Use the created flag in your code:

```go
advertisementFlag, err := helpers.GetBooleanFlag("advertisement")
if err != nil {
hc.logger.Error("error while checking flag from Flipt", zap.Error(err))
return err
}
if advertisementFlag.Enabled {
// Your logic here
}
```

### Use Variant Flag

1. Open [Flipt Flags](http://localhost:8081/#/flags) and create a new flag:
- Enter the name and key for the flag (e.g., `color`).
- Add a description, enable the flag, and click create.

2. Create variants for the flag:
- Add variants (e.g., `orange`, `green`).

3. Create a segment:
- Add constraints based on your requirements (e.g., `country` equals `ind`).

4. Create a rule:
- Assign the segment to the flag and specify the variant to return when the segment matches.

5. Use the created flag in your code:

```go
variantFlag, err := helpers.GetVarientFlag("color", "1234", map[string]string{"country": "ind"})
if err != nil {
hc.logger.Error("error while checking flag from Flipt", zap.Error(err))
return err
}
if variantFlag.Value == "orange" {
// Your logic here
} else if variantFlag.Value == "green" {
// Your logic here
}
```

- `GetVariantFlag` method:
```go
flagKey = "color"
entityId = "1234"
contextMap = map[string]string{"country": "ind"}
```

Here, `country: ind` is the constraint and `entityId: 1234` is the entity ID.
Loading

0 comments on commit 6ceca16

Please sign in to comment.