-
Notifications
You must be signed in to change notification settings - Fork 281
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #275 from gofr-dev/release/v0.3.0
Release/v0.3.0
- Loading branch information
Showing
40 changed files
with
1,013 additions
and
96 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,109 @@ | ||
# 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)) | ||
|
||
## 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. | ||
|
||
Create the following file in migrations directory. | ||
|
||
**Filename : 1708322067_create_employee_table.go** | ||
```go | ||
package migrations | ||
|
||
import "gofr.dev/pkg/gofr/migration" | ||
|
||
|
||
const createTable = `CREATE TABLE IF NOT EXISTS employee | ||
( | ||
id int not null | ||
primary key, | ||
name varchar(50) not null, | ||
gender varchar(6) not null, | ||
contact_number varchar(10) not null | ||
);` | ||
|
||
func createTableEmployee() migration.Migrate { | ||
return migration.Migrate{ | ||
UP: func(d migration.Datasource) error { | ||
_, err := d.DB.Exec(createTable) | ||
if err != nil { | ||
return err | ||
} | ||
return nil | ||
}, | ||
} | ||
} | ||
``` | ||
|
||
`migration.Datasource` have the datasources whose migrations are supported which are Redis and MySQL. | ||
All the migrations run in transactions by default. | ||
|
||
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** | ||
|
||
**Filename : all.go** | ||
```go | ||
package migrations | ||
|
||
import "gofr.dev/pkg/gofr/migration" | ||
|
||
func All() map[int64]migration.Migrate { | ||
return map[int64]migration.Migrate{ | ||
1708322067: createTableEmployee(), | ||
} | ||
} | ||
``` | ||
|
||
Migrations will run in ascending order of keys in this map. | ||
|
||
### Initialisation from main.go | ||
```go | ||
package main | ||
|
||
import ( | ||
"errors" | ||
"fmt" | ||
|
||
"gofr.dev/examples/using-migrations/migrations" | ||
"gofr.dev/pkg/gofr" | ||
) | ||
|
||
func main() { | ||
// Create a new application | ||
a := gofr.New() | ||
|
||
// Add migrations to run | ||
a.Migrate(migrations.All()) | ||
|
||
// Run the application | ||
a.Run() | ||
} | ||
|
||
``` | ||
|
||
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 | ||
``` | ||
|
||
|
||
|
||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
APP_NAME=using-migrations | ||
HTTP_PORT=9000 | ||
|
||
REDIS_HOST=localhost | ||
REDIS_PORT=2002 | ||
|
||
DB_HOST=localhost | ||
DB_USER=root | ||
DB_PASSWORD=password | ||
DB_NAME=gofr | ||
DB_PORT=2001 | ||
|
||
TRACER_HOST=localhost | ||
TRACER_PORT=2005 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,77 @@ | ||
package main | ||
|
||
import ( | ||
"errors" | ||
"fmt" | ||
|
||
"gofr.dev/examples/using-migrations/migrations" | ||
"gofr.dev/pkg/gofr" | ||
) | ||
|
||
const ( | ||
queryGetEmployee = "SELECT id,name,gender,contact_number,dob from employee where name = ?" | ||
queryInsertEmployee = "INSERT INTO employee (id, name, gender, contact_number,dob) values (?, ?, ?, ?, ?)" | ||
) | ||
|
||
func main() { | ||
// Create a new application | ||
a := gofr.New() | ||
|
||
// Add migrations to run | ||
a.Migrate(migrations.All()) | ||
|
||
// Add all the routes | ||
a.GET("/employee", GetHandler) | ||
a.POST("/employee", PostHandler) | ||
|
||
// Run the application | ||
a.Run() | ||
} | ||
|
||
type Employee struct { | ||
ID int `json:"id"` | ||
Name string `json:"name"` | ||
Gender string `json:"gender"` | ||
Phone int `json:"contact_number"` | ||
DOB string `json:"dob"` | ||
} | ||
|
||
// GetHandler handles GET requests for retrieving employee information | ||
func GetHandler(c *gofr.Context) (interface{}, error) { | ||
name := c.Param("name") | ||
if name == "" { | ||
return nil, errors.New("name can't be empty") | ||
} | ||
|
||
row := c.DB.QueryRowContext(c, queryGetEmployee, name) | ||
if row.Err() != nil { | ||
return nil, errors.New(fmt.Sprintf("DB Error : %v", row.Err())) | ||
} | ||
|
||
var emp Employee | ||
|
||
err := row.Scan(&emp.ID, &emp.Name, &emp.Gender, &emp.Phone, &emp.DOB) | ||
if err != nil { | ||
return nil, errors.New(fmt.Sprintf("DB Error : %v", err)) | ||
} | ||
|
||
return emp, nil | ||
} | ||
|
||
// PostHandler handles POST requests for creating new employees | ||
func PostHandler(c *gofr.Context) (interface{}, error) { | ||
var emp Employee | ||
if err := c.Bind(&emp); err != nil { | ||
c.Logger.Errorf("error in binding: %v", err) | ||
return nil, errors.New("invalid body") | ||
} | ||
|
||
// Execute the INSERT query | ||
_, err := c.DB.ExecContext(c, queryInsertEmployee, emp.ID, emp.Name, emp.Gender, emp.Phone, emp.DOB) | ||
|
||
if err != nil { | ||
return Employee{}, errors.New(fmt.Sprintf("DB Error : %v", err)) | ||
} | ||
|
||
return fmt.Sprintf("succesfully posted entity : %v", emp.Name), nil | ||
} |
39 changes: 39 additions & 0 deletions
39
examples/using-migrations/migrations/1708322067_create_employee_table.go
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
package migrations | ||
|
||
import ( | ||
"gofr.dev/pkg/gofr/migration" | ||
) | ||
|
||
const createTable = `CREATE TABLE IF NOT EXISTS employee | ||
( | ||
id int not null | ||
primary key, | ||
name varchar(50) not null, | ||
gender varchar(6) not null, | ||
contact_number varchar(10) not null | ||
);` | ||
|
||
const employee_date = `INSERT INTO employee (id, name, gender, contact_number) VALUES (1, 'Umang', "M", "0987654321");` | ||
|
||
func createTableEmployee() migration.Migrate { | ||
return migration.Migrate{ | ||
UP: func(d migration.Datasource) error { | ||
_, err := d.DB.Exec(createTable) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
_, err = d.DB.Exec(employee_date) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
_, err = d.DB.Exec("alter table employee add dob varchar(11) null;") | ||
if err != nil { | ||
return err | ||
} | ||
|
||
return nil | ||
}, | ||
} | ||
} |
20 changes: 20 additions & 0 deletions
20
examples/using-migrations/migrations/1708322089_redis_add_employee_name.go
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
package migrations | ||
|
||
import ( | ||
"context" | ||
"gofr.dev/pkg/gofr/migration" | ||
) | ||
|
||
func addEmployeeInRedis() migration.Migrate { | ||
return migration.Migrate{ | ||
UP: func(d migration.Datasource) error { | ||
err := d.Redis.Set(context.Background(), "Umang", "0987654321", 0).Err() | ||
if err != nil { | ||
return err | ||
} | ||
|
||
return nil | ||
|
||
}, | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
package migrations | ||
|
||
import ( | ||
"gofr.dev/pkg/gofr/migration" | ||
) | ||
|
||
func All() map[int64]migration.Migrate { | ||
return map[int64]migration.Migrate{ | ||
1708322067: createTableEmployee(), | ||
1708322089: addEmployeeInRedis(), | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.