Skip to content

Commit 1f19153

Browse files
committed
Adding database module
1 parent 76a4450 commit 1f19153

8 files changed

Lines changed: 880 additions & 5 deletions

File tree

README.md

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,9 +23,10 @@ Modular is a package that provides a structured way to create modular applicatio
2323

2424
Modular comes with a collection of reusable modules that you can incorporate into your applications:
2525

26-
| Module | Description | Documentation |
27-
|------------------------------------|---------------------------------|-------------------------------------------------|
28-
| [jsonschema](./modules/jsonschema) | JSON Schema validation services | [Documentation](./modules/jsonschema/README.md) |
26+
| Module | Description | Documentation |
27+
|------------------------------------|------------------------------------------|-------------------------------------------------|
28+
| [database](./modules/database) | Database connectivity and SQL operations | [Documentation](./modules/database/README.md) |
29+
| [jsonschema](./modules/jsonschema) | JSON Schema validation services | [Documentation](./modules/jsonschema/README.md) |
2930

3031
For more information about the available modules, see the [modules directory](./modules).
3132

modules/README.md

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ This directory contains a collection of reusable modules for the [Modular](https
66

77
| Module | Description | Documentation |
88
|----------------------------|------------------------------------------|-----------------------------------------|
9+
| [database](./database) | Database connectivity and SQL operations | [Documentation](./database/README.md) |
910
| [jsonschema](./jsonschema) | Provides JSON Schema validation services | [Documentation](./jsonschema/README.md) |
1011

1112
## Using Modules
@@ -16,10 +17,12 @@ Each module can be imported and used independently:
1617
import (
1718
"github.com/GoCodeAlone/modular"
1819
"github.com/GoCodeAlone/modular/modules/jsonschema"
20+
"github.com/GoCodeAlone/modular/modules/database"
1921
)
2022

21-
// Register the module with your Modular application
22-
app.RegisterModule(jsonschema.NewModule(app))
23+
// Register the modules with your Modular application
24+
app.RegisterModule(jsonschema.NewModule())
25+
app.RegisterModule(database.NewModule())
2326
```
2427

2528
## Module Structure

modules/database/README.md

Lines changed: 338 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,338 @@
1+
# Database Module for Modular
2+
3+
[![Go Reference](https://pkg.go.dev/badge/github.com/GoCodeAlone/modular/modules/database.svg)](https://pkg.go.dev/github.com/GoCodeAlone/modular/modules/database)
4+
5+
A [Modular](https://github.com/GoCodeAlone/modular) module that provides database connectivity and management.
6+
7+
## Overview
8+
9+
The Database module provides a service for connecting to and interacting with SQL databases. It wraps the standard Go `database/sql` package to provide a clean, service-oriented interface that integrates with the Modular framework.
10+
11+
## Features
12+
13+
- Support for multiple database connections with named configurations
14+
- Connection pooling with configurable settings
15+
- Simplified interface for common database operations
16+
- Context-aware database operations for proper cancellation and timeout handling
17+
- Support for transactions
18+
19+
## Installation
20+
21+
```bash
22+
go get github.com/GoCodeAlone/modular/modules/database
23+
```
24+
25+
## Usage
26+
27+
### Importing Database Drivers
28+
29+
The database module uses the standard Go `database/sql` package, which requires you to import the specific database driver you plan to use as a side-effect. Make sure to import your desired driver in your main package:
30+
31+
```go
32+
import (
33+
"github.com/GoCodeAlone/modular"
34+
"github.com/GoCodeAlone/modular/modules/database"
35+
36+
// Import database drivers as needed
37+
_ "github.com/lib/pq" // PostgreSQL driver
38+
_ "github.com/go-sql-driver/mysql" // MySQL driver
39+
_ "github.com/mattn/go-sqlite3" // SQLite driver
40+
)
41+
```
42+
43+
You'll also need to add the driver to your module's dependencies:
44+
45+
```bash
46+
# For PostgreSQL
47+
go get github.com/lib/pq
48+
49+
# For MySQL
50+
go get github.com/go-sql-driver/mysql
51+
52+
# For SQLite
53+
go get github.com/mattn/go-sqlite3
54+
```
55+
56+
### Registering the Module
57+
58+
```go
59+
import (
60+
"github.com/GoCodeAlone/modular"
61+
"github.com/GoCodeAlone/modular/modules/database"
62+
_ "github.com/lib/pq" // Import PostgreSQL driver
63+
)
64+
65+
func main() {
66+
app := modular.NewStdApplication(
67+
modular.NewStdConfigProvider(configMap),
68+
logger,
69+
)
70+
71+
// Register the database module
72+
app.RegisterModule(database.NewModule())
73+
74+
// Register your modules that depend on the database service
75+
app.RegisterModule(NewYourModule())
76+
77+
// Run the application
78+
if err := app.Run(); err != nil {
79+
logger.Error("Application error", "error", err)
80+
}
81+
}
82+
```
83+
84+
### Configuration
85+
86+
Configure the database module in your application configuration:
87+
88+
```yaml
89+
database:
90+
default: "postgres_main"
91+
connections:
92+
postgres_main:
93+
driver: "postgres"
94+
dsn: "postgres://user:password@localhost:5432/dbname?sslmode=disable"
95+
max_open_connections: 25
96+
max_idle_connections: 5
97+
connection_max_lifetime: 300 # seconds
98+
connection_max_idle_time: 60 # seconds
99+
mysql_reporting:
100+
driver: "mysql"
101+
dsn: "user:password@tcp(localhost:3306)/reporting"
102+
max_open_connections: 10
103+
max_idle_connections: 2
104+
connection_max_lifetime: 600 # seconds
105+
```
106+
107+
### Using the Database Service
108+
109+
```go
110+
type YourModule struct {
111+
dbService database.DatabaseService
112+
}
113+
114+
// Request the database service
115+
func (m *YourModule) RequiresServices() []modular.ServiceDependency {
116+
return []modular.ServiceDependency{
117+
{
118+
Name: "database.service",
119+
Required: true,
120+
},
121+
// If you need a specific database connection:
122+
{
123+
Name: "database.service.mysql_reporting",
124+
Required: true,
125+
},
126+
}
127+
}
128+
129+
// Inject the service using constructor injection
130+
func (m *YourModule) Constructor() modular.ModuleConstructor {
131+
return func(app *modular.StdApplication, services map[string]any) (modular.Module, error) {
132+
// Get the default database service
133+
dbService, ok := services["database.service"].(database.DatabaseService)
134+
if !ok {
135+
return nil, fmt.Errorf("service 'database.service' not found or wrong type")
136+
}
137+
138+
// Get a specific database connection
139+
reportingDB, ok := services["database.service.mysql_reporting"].(database.DatabaseService)
140+
if !ok {
141+
return nil, fmt.Errorf("service 'database.service.mysql_reporting' not found or wrong type")
142+
}
143+
144+
return &YourModule{
145+
dbService: dbService,
146+
}, nil
147+
}
148+
}
149+
150+
// Example of using the database service
151+
func (m *YourModule) GetUserData(ctx context.Context, userID int64) (*User, error) {
152+
user := &User{}
153+
154+
row := m.dbService.QueryRowContext(ctx,
155+
"SELECT id, name, email FROM users WHERE id = $1", userID)
156+
157+
err := row.Scan(&user.ID, &user.Name, &user.Email)
158+
if err != nil {
159+
if err == sql.ErrNoRows {
160+
return nil, fmt.Errorf("user not found: %d", userID)
161+
}
162+
return nil, err
163+
}
164+
165+
return user, nil
166+
}
167+
168+
// Example of a transaction
169+
func (m *YourModule) TransferFunds(ctx context.Context, fromAccount, toAccount int64, amount float64) error {
170+
tx, err := m.dbService.BeginTx(ctx, nil)
171+
if err != nil {
172+
return err
173+
}
174+
175+
defer func() {
176+
if err != nil {
177+
tx.Rollback()
178+
return
179+
}
180+
}()
181+
182+
// Debit from source account
183+
_, err = tx.ExecContext(ctx,
184+
"UPDATE accounts SET balance = balance - $1 WHERE id = $2", amount, fromAccount)
185+
if err != nil {
186+
return err
187+
}
188+
189+
// Credit to destination account
190+
_, err = tx.ExecContext(ctx,
191+
"UPDATE accounts SET balance = balance + $1 WHERE id = $2", amount, toAccount)
192+
if err != nil {
193+
return err
194+
}
195+
196+
// Commit the transaction
197+
return tx.Commit()
198+
}
199+
```
200+
201+
### Working with multiple database connections
202+
203+
```go
204+
type MultiDBModule struct {
205+
dbManager *database.Module
206+
}
207+
208+
// Request the database manager
209+
func (m *MultiDBModule) RequiresServices() []modular.ServiceDependency {
210+
return []modular.ServiceDependency{
211+
{
212+
Name: "database.manager",
213+
Required: true,
214+
},
215+
}
216+
}
217+
218+
// Inject the manager using constructor injection
219+
func (m *MultiDBModule) Constructor() modular.ModuleConstructor {
220+
return func(app *modular.StdApplication, services map[string]any) (modular.Module, error) {
221+
dbManager, ok := services["database.manager"].(*database.Module)
222+
if !ok {
223+
return nil, fmt.Errorf("service 'database.manager' not found or wrong type")
224+
}
225+
226+
return &MultiDBModule{
227+
dbManager: dbManager,
228+
}, nil
229+
}
230+
}
231+
232+
// Example of using multiple database connections
233+
func (m *MultiDBModule) ProcessData(ctx context.Context) error {
234+
// Get specific connections
235+
sourceDB, exists := m.dbManager.GetConnection("postgres_main")
236+
if !exists {
237+
return fmt.Errorf("source database connection not found")
238+
}
239+
240+
reportingDB, exists := m.dbManager.GetConnection("mysql_reporting")
241+
if !exists {
242+
return fmt.Errorf("reporting database connection not found")
243+
}
244+
245+
// Read from source
246+
rows, err := sourceDB.QueryContext(ctx, "SELECT id, data FROM source_table")
247+
if err != nil {
248+
return err
249+
}
250+
defer rows.Close()
251+
252+
// Process and write to reporting
253+
for rows.Next() {
254+
var id int64
255+
var data string
256+
257+
if err := rows.Scan(&id, &data); err != nil {
258+
return err
259+
}
260+
261+
// Process and write to reporting DB
262+
_, err = reportingDB.ExecuteContext(ctx,
263+
"INSERT INTO processed_data (source_id, data) VALUES (?, ?)",
264+
id, processData(data))
265+
if err != nil {
266+
return err
267+
}
268+
}
269+
270+
return rows.Err()
271+
}
272+
```
273+
274+
## API Reference
275+
276+
### Types
277+
278+
#### DatabaseService
279+
280+
```go
281+
type DatabaseService interface {
282+
// Connect establishes the database connection
283+
Connect() error
284+
285+
// Close closes the database connection
286+
Close() error
287+
288+
// DB returns the underlying database connection
289+
DB() *sql.DB
290+
291+
// Ping verifies the database connection is still alive
292+
Ping(ctx context.Context) error
293+
294+
// Stats returns database statistics
295+
Stats() sql.DBStats
296+
297+
// ExecuteContext executes a query without returning any rows
298+
ExecuteContext(ctx context.Context, query string, args ...interface{}) (sql.Result, error)
299+
300+
// Execute executes a query without returning any rows (using default context)
301+
Execute(query string, args ...interface{}) (sql.Result, error)
302+
303+
// QueryContext executes a query that returns rows
304+
QueryContext(ctx context.Context, query string, args ...interface{}) (*sql.Rows, error)
305+
306+
// Query executes a query that returns rows (using default context)
307+
Query(query string, args ...interface{}) (*sql.Rows, error)
308+
309+
// QueryRowContext executes a query that returns a single row
310+
QueryRowContext(ctx context.Context, query string, args ...interface{}) *sql.Row
311+
312+
// QueryRow executes a query that returns a single row (using default context)
313+
QueryRow(query string, args ...interface{}) *sql.Row
314+
315+
// BeginTx starts a transaction
316+
BeginTx(ctx context.Context, opts *sql.TxOptions) (*sql.Tx, error)
317+
318+
// Begin starts a transaction with default options
319+
Begin() (*sql.Tx, error)
320+
}
321+
```
322+
323+
#### Module Manager Methods
324+
325+
```go
326+
// GetConnection returns a database service by name
327+
func (m *Module) GetConnection(name string) (DatabaseService, bool)
328+
329+
// GetDefaultConnection returns the default database service
330+
func (m *Module) GetDefaultConnection() DatabaseService
331+
332+
// GetConnections returns all configured database connections
333+
func (m *Module) GetConnections() map[string]DatabaseService
334+
```
335+
336+
## License
337+
338+
[MIT License](../../LICENSE)

0 commit comments

Comments
 (0)