Skip to content

Commit

Permalink
Merge pull request #336 from gofr-dev/release/v1.0.0
Browse files Browse the repository at this point in the history
Release v1.0.0
  • Loading branch information
aryanmehrotra authored Feb 27, 2024
2 parents 80cdfd4 + 1a89477 commit c068bf2
Show file tree
Hide file tree
Showing 111 changed files with 7,075 additions and 386 deletions.
4 changes: 4 additions & 0 deletions .github/workflows/docs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,10 @@ name: Build and Deploy
push:
paths:
- 'docs/**'
pull_request:
paths:
- 'docs/**'

env:
APP_NAME: gofr-web
REGISTRY: gcr.io
Expand Down
67 changes: 63 additions & 4 deletions .github/workflows/go.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,61 @@ on:
- 'docs/**'

jobs:
MIGRATION-Unit-Testing:
name: Migration Unit Testing🛠
runs-on: ubuntu-latest

steps:
- name: Checkout code into go module directory
uses: actions/checkout@v4
with:
fetch-depth: 0

- name: Set up Go 1.21
uses: actions/setup-go@v4
with:
go-version: 1.21
id: Go

- name: Get dependencies
run: |
go mod download
- name: Test
run: |
export GOFR_ENV=test
go test gofr.dev/pkg/gofr/migration... -v -short -coverprofile profile.cov -coverpkg=gofr.dev/pkg/gofr/migration...
go tool cover -func profile.cov
- name: Upload Test Coverage
uses: actions/upload-artifact@v3
with:
name: MIGRATION-Coverage-Report
path: profile.cov

Example-Unit-Testing:
name: Example Unit Testing🛠
runs-on: ubuntu-latest
services:
zookeeper:
image: wurstmeister/zookeeper
ports:
- "2181:2181"

kafka:
image: wurstmeister/kafka
ports:
- "9092:9092"
env:
KAFKA_ADVERTISED_LISTENERS: 'PLAINTEXT://localhost:9092'
KAFKA_LISTENER_SECURITY_PROTOCOL_MAP: 'PLAINTEXT:PLAINTEXT,PLAINTEXT_HOST:PLAINTEXT'
KAFKA_INTER_BROKER_LISTENER_NAME: 'PLAINTEXT'
KAFKA_ADVERTISED_HOST_NAME: 'localhost'
KAFKA_LISTENERS: 'PLAINTEXT://0.0.0.0:9092'
KAFKA_AUTO_CREATE_TOPICS_ENABLE: true
KAFKA_ZOOKEEPER_CONNECT: 'zookeeper:2181'

redis:
image: redis:7.0.5
ports:
Expand Down Expand Up @@ -49,13 +100,19 @@ jobs:
run: |
go mod download
- name: Create Kafka topics for test
run: |
docker exec ${{ job.services.kafka.id }} kafka-topics.sh --create --bootstrap-server localhost:9092 --replication-factor 1 --partitions 1 --topic products
docker exec ${{ job.services.kafka.id }} kafka-topics.sh --create --bootstrap-server localhost:9092 --replication-factor 1 --partitions 1 --topic order-logs
- name: Start Zipkin
run: docker run -d -p 2005:9411 openzipkin/zipkin:latest

- name: Test
run: |
export GOFR_ENV=test
go test gofr.dev/examples/... -v -short -coverprofile profile.cov -coverpkg=gofr.dev/examples/...
go test gofr.dev/examples/... -v -short -coverprofile packageWithpbgo.cov -coverpkg=gofr.dev/examples/...
grep -vE '^gofr\.dev\/.*\.pb\.go' packageWithpbgo.cov > profile.cov
go tool cover -func profile.cov
- name: Upload Test Coverage
Expand Down Expand Up @@ -87,7 +144,8 @@ jobs:
- name: Test
run: |
export GOFR_ENV=test
go test gofr.dev/pkg/... -v -short -coverprofile profile.cov -coverpkg=gofr.dev/pkg/...
go test gofr.dev/pkg/... -tags migration -v -short -coverprofile packageWithMigration.cov -coverpkg=gofr.dev/pkg/...
grep -v 'gofr.dev/pkg/gofr/migration' packageWithMigration.cov > profile.cov
go tool cover -func profile.cov
- name: Upload Test Coverage
Expand All @@ -99,7 +157,7 @@ jobs:
parse_coverage:
name: Code Coverage
runs-on: ubuntu-latest
needs: [Example-Unit-Testing,PKG-Unit-Testing]
needs: [Example-Unit-Testing,PKG-Unit-Testing,MIGRATION-Unit-Testing]
steps:
- name: Check out code into the Go module directory
uses: actions/checkout@v4
Expand All @@ -114,6 +172,7 @@ jobs:
run: |
awk '!/^mode: / && FNR==1{print "mode: set"} {print}' ./Example-Test-Report/profile.cov > merged_profile.cov
tail -n +2 ./PKG-Coverage-Report/profile.cov >> merged_profile.cov
tail -n +2 ./MIGRATION-Coverage-Report/profile.cov >> merged_profile.cov
- name: Parse code-coverage value
working-directory: artifacts
Expand All @@ -131,7 +190,7 @@ jobs:
upload_coverage:
name: Upload Coverage📊
runs-on: ubuntu-latest
needs: [Example-Unit-Testing,PKG-Unit-Testing]
needs: [Example-Unit-Testing,PKG-Unit-Testing,MIGRATION-Unit-Testing]
if: ${{ github.event_name == 'push' && github.ref == 'refs/heads/development' }}
steps:
- name: Check out code into the Go module directory
Expand Down
3 changes: 3 additions & 0 deletions docs/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,13 @@ FROM ghcr.io/gofr-dev/website:latest
WORKDIR /app

COPY docs/quick-start /app/src/app/docs/quick-start
COPY docs/public /app/public
COPY docs/advanced-guide /app/src/app/docs/advanced-guide
COPY docs/references /app/src/app/docs/references
COPY docs/page.md /app/src/app/docs
COPY docs/navigation.js /app/src/lib


ENV NODE_ENV production

RUN npm install
Expand Down
46 changes: 46 additions & 0 deletions docs/advanced-guide/circuit-breaker/page.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
# Circuit Breaker in HTTP Communication

Calls to remote resources and services can fail due to temporary issues like slow network connections or timeouts, as well as longer-lasting problems such as service unavailability. While transient faults can be mitigated using the "Retry pattern," there are cases where continual retries are futile, such as during severe service failures.

In such scenarios, it's crucial for applications to recognize when an operation is unlikely to succeed and handle the failure appropriately rather than persistently retrying. Indiscriminate use of HTTP retries can even lead to unintentional denial-of-service attacks within the software itself, as multiple clients may flood a failing service with retry attempts.

To prevent this, a defense mechanism like the circuit breaker pattern is essential. Unlike the "Retry pattern" which aims to eventually succeed, the circuit breaker pattern focuses on preventing futile operations. While these patterns can be used together, it's vital for the retry logic to be aware of the circuit breaker's feedback and cease retries if the circuit breaker indicates a non-transient fault.

GoFr inherently provides the functionality, it can be enabled by passing circuit breaker configs as options to `AddHTTPService()` method.

## Usage

```go
package main

import (
"time"

"gofr.dev/pkg/gofr"
"gofr.dev/pkg/gofr/service"
)

func main() {
// Create a new application
app := gofr.New()

app.AddHTTPService("order", "https://order-func",
&service.CircuitBreakerConfig{
// Number of consecutive failed requests after which circuit breaker will be enabled
Threshold: 4,
// Time interval at which circuit breaker will hit the aliveness endpoint.
Interval: 1 * time.Second,
},
)

app.GET("/order", Get)

// Run the application
app.Run()
}
```

Circuit breaker state changes to open when number of consecutive failed requests increases the threshold.
When it is in open state, GoFr makes request to the aliveness endpoint (default being - /.well-known/alive) at an equal interval of time provided in config.

To override the default aliveness endpoint [refer](/docs/advanced-guide/monitoring-service-health)
35 changes: 35 additions & 0 deletions docs/advanced-guide/custom-spans-in-tracing/page.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# Custom Spans In Tracing

GoFr's built-in tracing provides valuable insights into your application's behavior. However, sometimes you might need
even more granular details about specific operations within your application. This is where `custom spans` come in.

## How it helps?
By adding custom spans in traces to your requests, you can:

- **Gain granular insights:** Custom spans allow you to track specific operations or functions within your application,
providing detailed performance data.
- **Identify bottlenecks:** By analyzing custom spans, you can pinpoint areas of your code that may be causing
performance bottlenecks or inefficiencies.
- **Improve debugging:** Custom spans enhance your ability to debug issues by providing visibility into the execution
flow of your application.

## Usage

To add a custom trace to a request, you can use the `Trace()` method of GoFr context, which takes the name of the span as an argument
and returns a trace.Span.

```go
func MyHandler(c context.Context) error {
span := c.Trace("my-custom-span")
defer span.Close()

// Do some work here
return nil
}
```

In this example, **my-custom-span** is the name of the custom span that you want to add to the request.
The defer statement ensures that the span is closed even if an error occurs to ensure that the trace is properly recorded.



78 changes: 58 additions & 20 deletions docs/advanced-guide/handling-data-migrations/page.md
Original file line number Diff line number Diff line change
@@ -1,28 +1,22 @@
# Handling Data Migrations

Gofr supports data migrations for MySQL and Redis which allows to alter the state of a database, be it adding a new column to existing table or modifying the data type of an existing column or adding constraints to an existing table, setting and removing keys etc.

**How Migrations help?**

Suppose you manually edit fragments of your database, and now it's your responsibility to inform other developers to execute them. Additionally, you need to keep track of which changes should be applied to production machines in the next deployment.

GoFr maintains the table called **gofr_migration** which helps in such case. This table tracks which migrations have already been executed and ensures that only migrations that have never been run are executed. This way, you only need to ensure that your migrations are properly in place. ([Learn more](https://cloud.google.com/architecture/database-migration-concepts-principles-part-1))
Suppose you manually make changes to your database, and now it's your responsibility to inform other developers to execute them. Additionally, you need to keep track of which changes should be applied to production machines in the next deployment.
Gofr supports data migrations for MySQL, Postgres and Redis which allows to alter the state of a database, be it adding a new column to existing table or modifying the data type of existing column or adding constraints to an existing table, setting and removing keys etc.

## Usage

We will create an employee table using migrations.

### Creating Migration Files

It is recommended to maintain a migrations directory in your project root to enhance readability and maintainability.

**Migration file names**

It is recommended that each migration file should be numbered using the unix timestamp when the migration was created, This helps prevent numbering conflicts when working in a team environment.
It is recommended that each migration file should be numbered in the format of *YYYYMMDDHHMMSS* when the migration was created.
This helps prevent numbering conflicts and allows for maintaining the correct sort order by name in different filesystem views.

Create the following file in migrations directory.

**Filename : 1708322067_create_employee_table.go**
**Filename : 20240226153000_create_employee_table.go**
```go
package migrations

Expand All @@ -41,7 +35,7 @@ const createTable = `CREATE TABLE IF NOT EXISTS employee
func createTableEmployee() migration.Migrate {
return migration.Migrate{
UP: func(d migration.Datasource) error {
_, err := d.DB.Exec(createTable)
_, err := d.SQL.Exec(createTable)
if err != nil {
return err
}
Expand All @@ -51,10 +45,10 @@ func createTableEmployee() migration.Migrate {
}
```

`migration.Datasource` have the datasources whose migrations are supported which are Redis and MySQL.
All the migrations run in transactions by default.
`migration.Datasource` have the datasources whose migrations are supported i.e. Redis and SQL (MySQL and PostgreSQL).
All the migrations always run in a transaction.

For MySQL it is highly recommended to use `IF EXISTS` and `IF NOT EXIST` in DDL commands as MySQL implicitly commits these Commands.
For MySQL it is highly recommended to use `IF EXISTS` and `IF NOT EXIST` in DDL commands as MySQL implicitly commits these commands.

**Create a function which returns all the migrations in a map**

Expand All @@ -66,7 +60,7 @@ import "gofr.dev/pkg/gofr/migration"

func All() map[int64]migration.Migrate {
return map[int64]migration.Migrate{
1708322067: createTableEmployee(),
20240226153000: createTableEmployee(),
}
}
```
Expand All @@ -78,9 +72,6 @@ Migrations will run in ascending order of keys in this map.
package main

import (
"errors"
"fmt"

"gofr.dev/examples/using-migrations/migrations"
"gofr.dev/pkg/gofr"
)
Expand All @@ -101,9 +92,56 @@ func main() {
When we run the app we will see the following logs for migrations which ran successfully.

```bash
INFO [16:55:46] Migration 1708322067 ran successfully
INFO [16:55:46] Migration 20240226153000 ran successfully
```




GoFr maintains the records in the database itself which helps in tracking which migrations have already been executed and ensures that only migrations that have never been run are executed.
This way, you only need to ensure that your migrations are properly in place. ([Learn more](https://cloud.google.com/architecture/database-migration-concepts-principles-part-1))

## Migration Records

**SQL**

Migration records are stored and maintained in **gofr_migrations** table which has the following schema:

{% table %}
* Field
* Type
---
* version
* bigint
---
* method
* varchar(4)
---
* start_time
* timestamp
---
* duration
* bigint
---
{% /table %}

**REDIS**

Migration records are stored and maintained in a Redis Hash named **gofr_migrations** where key is the version and value contains other details in JSON format.

Example :

Key : 20240226153000

Value : {"method":"UP","startTime":"2024-02-26T15:03:46.844558+05:30","duration":0}

Where,

**Version** : Migration version is the number provided in the map, i.e. sequence number.

**Start Time** : Time when Migration Started in UTC.

**Duration** : Time taken by Migration since it started in milliseconds.

**Method** : It contains the method(UP/DOWN) in which migration ran.
(For now only method UP is supported)
Loading

0 comments on commit c068bf2

Please sign in to comment.