Skip to content

Commit fb1607c

Browse files
authored
feat: support multi-master connection (#11)
* refactor: support multi-master connection * chore: add comments * chore: fix typo * chore: fix lint * refactor: increase module version * chore: add TODO * chore: fix data-struct * chore: fix linter * chore: remove todo * chore: fix struct's name * chore: add generic and option * chore: fix struct receiver * chore: fix example * chore: update readme * chore: add example multi-primary * chore: add more example * chore: change generic type * chore: test round robin logic * chore: fix readme
1 parent 3558e7f commit fb1607c

14 files changed

+515
-191
lines changed

.golangci.yaml

+3-2
Original file line numberDiff line numberDiff line change
@@ -132,8 +132,9 @@ issues:
132132
- path: _test\.go
133133
linters:
134134
- gomnd
135-
135+
- path: loadbalancer.go
136+
text: "G404: Use of weak random number generator" #expected, just for randomLB policy
136137
run:
137138
timeout: 5m
138-
go: "1.17" # TODO(ldez): we force to use an old version of Go for the CI and the tests.
139+
go: "1.19"
139140
skip-dirs: []

README.md

+93-22
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,32 @@
11
# dbresolver
2+
23
Golang Database Resolver and Wrapper for any multiple database connections topology, eg. master-slave replication database, cross-region application.
34

45
[![Go](https://github.com/bxcodec/dbresolver/actions/workflows/go.yml/badge.svg?branch=main)](https://github.com/bxcodec/dbresolver/actions/workflows/go.yml)
56
[![Go.Dev](https://img.shields.io/badge/go.dev-reference-007d9c?logo=go&logoColor=white)](https://pkg.go.dev/github.com/bxcodec/dbresolver?tab=doc)
67

78
## Idea and Inspiration
89

9-
This DBResolver library will split your connections to correct defined DBs. Eg, all read query will routed to ReadOnly replica db, and all write operation(Insert, Update, Delete) will routed to Primary/Master DB.
10+
This DBResolver library will split your connections to correct defined DBs. Eg, all read query will routed to ReadOnly replica db, and all write operation(Insert, Update, Delete) will routed to Primary/Master DB.
1011

1112
Read more for the explanation on this [blog post](https://betterprogramming.pub/create-a-cross-region-rdbms-connection-library-with-dbresolver-5072bed6a7b8)
1213

1314
### Usecase 1: Separated RW and RO Database connection
15+
1416
<details open>
1517

1618
<summary>Click to Expand</summary>
1719

1820
- You have your application deployed
1921
- Your application is heavy on read operations
2022
- Your DBs replicated to multiple replicas for faster queries
21-
- You separate the connections for optimized query
22-
- ![image](https://user-images.githubusercontent.com/11002383/180010864-c9e2a0b6-520d-48d6-bf0d-490eb070e75d.png)
23+
- You separate the connections for optimized query
24+
- ![image](https://user-images.githubusercontent.com/11002383/180010864-c9e2a0b6-520d-48d6-bf0d-490eb070e75d.png)
2325

2426
</details>
2527

2628
### Usecases 2: Cross Region Database
29+
2730
<details open>
2831

2932
<summary>Click to Expand</summary>
@@ -37,19 +40,20 @@ Read more for the explanation on this [blog post](https://betterprogramming.pub/
3740
## Support
3841

3942
You can file an [Issue](https://github.com/bxcodec/dbresolver/issues/new).
40-
See documentation in [Go.Dev](https://pkg.go.dev/github.com/bxcodec/dbresolver?tab=doc)
43+
See documentation in [Go.Dev](https://pkg.go.dev/github.com/bxcodec/dbresolver/v2?tab=doc)
4144

4245
## Getting Started
4346

4447
#### Download
4548

4649
```shell
47-
go get -u github.com/bxcodec/dbresolver
50+
go get -u github.com/bxcodec/dbresolver/v2
4851
```
4952

5053
# Example
5154

52-
### With Multi *sql.DB
55+
### With Multi \*sql.DB
56+
5357
<details open>
5458

5559
<summary>Click to Expand</summary>
@@ -61,12 +65,13 @@ import (
6165
"context"
6266
"database/sql"
6367
"fmt"
68+
"log"
6469

65-
"github.com/bxcodec/dbresolver"
70+
"github.com/bxcodec/dbresolver/v2"
6671
_ "github.com/lib/pq"
6772
)
6873

69-
func main() {
74+
func ExampleWrapDBs() {
7075
var (
7176
host1 = "localhost"
7277
port1 = 5432
@@ -85,36 +90,41 @@ func main() {
8590
// open database for primary
8691
dbPrimary, err := sql.Open("postgres", rwPrimary)
8792
if err != nil {
88-
panic(err)
93+
log.Print("go error when connecting to the DB")
8994
}
90-
//configure the DBs for other setup eg, tracing, etc
95+
// configure the DBs for other setup eg, tracing, etc
9196
// eg, tracing.Postgres(dbPrimary)
9297

9398
// open database for replica
9499
dbReadOnlyReplica, err := sql.Open("postgres", readOnlyReplica)
95100
if err != nil {
96-
panic(err)
101+
log.Print("go error when connecting to the DB")
97102
}
98-
//configure the DBs for other setup eg, tracing, etc
103+
// configure the DBs for other setup eg, tracing, etc
99104
// eg, tracing.Postgres(dbReadOnlyReplica)
100105

101-
connectionDB := dbresolver.WrapDBs(dbPrimary, dbReadOnlyReplica)
106+
connectionDB := dbresolver.NewResolver(
107+
dbresolver.WithPrimaryDBs(dbPrimary),
108+
dbresolver.WithReplicaDBs(dbReadOnlyReplica),
109+
dbresolver.WithLoadBalancer(dbresolver.RoundRobinLB))
102110

103-
//now you can use the connection for all DB operation
111+
// now you can use the connection for all DB operation
104112
_, err = connectionDB.ExecContext(context.Background(), "DELETE FROM book WHERE id=$1") // will use primaryDB
105113
if err != nil {
106-
panic(err)
114+
log.Print("go error when executing the query to the DB", err)
107115
}
108116
_ = connectionDB.QueryRowContext(context.Background(), "SELECT * FROM book WHERE id=$1") // will use replicaReadOnlyDB
109117

118+
// Output:
119+
//
110120
}
111121

112122
```
113123

114124
</details>
115125

116-
117126
### With Multi Connection String
127+
118128
<details open>
119129

120130
<summary>Click to Expand</summary>
@@ -125,12 +135,13 @@ package main
125135
import (
126136
"context"
127137
"fmt"
138+
"log"
128139

129-
"github.com/bxcodec/dbresolver"
140+
"github.com/bxcodec/dbresolver/v2"
130141
_ "github.com/lib/pq"
131142
)
132143

133-
func main() {
144+
func ExampleOpen() {
134145
var (
135146
host1 = "localhost"
136147
port1 = 5432
@@ -147,17 +158,76 @@ func main() {
147158
readOnlyReplica := fmt.Sprintf("host=%s port=%d user=%s password=%s dbname=%s sslmode=disable", host2, port2, user2, password2, dbname)
148159
connectionDB, err := dbresolver.Open("postgres", fmt.Sprintf("%s;%s", rwPrimary, readOnlyReplica))
149160
if err != nil {
150-
panic(err)
161+
log.Print("go error when connecting to the DB", err)
151162
}
152163

153-
//now you can use the connection for all DB operation
164+
// now you can use the connection for all DB operation
154165
_, err = connectionDB.ExecContext(context.Background(), "DELETE FROM book WHERE id=$1") // will use primaryDB
155166
if err != nil {
156-
panic(err)
167+
log.Print("go error when connecting to the DB", err)
157168
}
158169
_ = connectionDB.QueryRowContext(context.Background(), "SELECT * FROM book WHERE id=$1") // will use replicaReadOnlyDB
159170

171+
// Output:
172+
//
173+
}
160174

175+
```
176+
177+
</details>
178+
179+
### With Multi Master (Primary) Connection String
180+
181+
<details open>
182+
183+
<summary>Click to Expand</summary>
184+
185+
```go
186+
package main
187+
188+
import (
189+
"context"
190+
"fmt"
191+
"log"
192+
193+
"github.com/bxcodec/dbresolver/v2"
194+
_ "github.com/lib/pq"
195+
)
196+
197+
func ExampleOpenMultiPrimary() {
198+
var (
199+
host1 = "localhost"
200+
port1 = 5432
201+
user1 = "postgresrw"
202+
password1 = "<password>"
203+
host2 = "localhost"
204+
port2 = 5433
205+
user2 = "postgresro"
206+
password2 = "<password>"
207+
dbname = "<dbname>"
208+
)
209+
// connection string
210+
rwPrimary1 := fmt.Sprintf("host=%s port=%d user=%s password=%s dbname=%s sslmode=disable", host1, port1, user1, password1, dbname)
211+
rwPrimary2 := fmt.Sprintf("host=%s port=%d user=%s password=%s dbname=%s sslmode=disable", host2, port2, user2, password2, dbname)
212+
readOnlyReplica1 := fmt.Sprintf("host=%s port=%d user=%s password=%s dbname=%s sslmode=disable", host1, port1, user1, password1, dbname)
213+
readOnlyReplica2 := fmt.Sprintf("host=%s port=%d user=%s password=%s dbname=%s sslmode=disable", host2, port2, user2, password2, dbname)
214+
215+
rwPrimary := fmt.Sprintf("%s;%s", rwPrimary1, rwPrimary2)
216+
readOnlyReplica := fmt.Sprintf("%s;%s", readOnlyReplica1, readOnlyReplica2)
217+
connectionDB, err := dbresolver.Open("postgres", fmt.Sprintf("%s;%s", rwPrimary, readOnlyReplica))
218+
if err != nil {
219+
log.Print("go error when connecting to the DB", err)
220+
}
221+
222+
// now you can use the connection for all DB operation
223+
_, err = connectionDB.ExecContext(context.Background(), "DELETE FROM book WHERE id=$1") // will use primaryDB
224+
if err != nil {
225+
log.Print("go error when connecting to the DB", err)
226+
}
227+
_ = connectionDB.QueryRowContext(context.Background(), "SELECT * FROM book WHERE id=$1") // will use replicaReadOnlyDB
228+
229+
// Output:
230+
//
161231
}
162232

163233
```
@@ -166,7 +236,7 @@ func main() {
166236

167237
## Important Notes
168238

169-
- Primary Database will be used when you call these functions
239+
- Primary Database will be used when you call these functions
170240
- `Exec`
171241
- `ExecContext`
172242
- `Begin` (transaction will use primary)
@@ -178,6 +248,7 @@ func main() {
178248
- `QueryRowContext`
179249

180250
## Contribution
251+
181252
---
182253

183254
To contrib to this project, you can open a PR or an issue.

0 commit comments

Comments
 (0)