feat: update methods & improved demo

This commit is contained in:
Florian Sylvain
2025-05-02 02:23:47 +02:00
parent dde8487382
commit 72eacc461e
8 changed files with 123 additions and 27 deletions
-1
View File
@@ -1,2 +1 @@
tmp/
data/
+26 -9
View File
@@ -4,7 +4,7 @@ This project demonstrates a proof of concept for a database solution in Go that
## Disclaimer ⚠️
This is not clean architecture friendly yet.
This project is not clean architecture friendly, yet.
## Core Features
@@ -15,23 +15,23 @@ This is not clean architecture friendly yet.
## Project Structure
- **main.go**: Example usage demonstrating user creation and address management
- **store/db.go**: Database connection management
- **store/user.go**: User entity operations (create, find, list)
- **main.go**: Example usage demonstrating CRUD operations and relationships
- **store/db.go**: Database connection management with in-memory storage
- **store/user.go**: User entity operations (create, find, update, list)
- **store/address.go**: Address entity operations with user relationships
## How It Works
### Database Management
The project uses BadgerDB as the underlying storage engine. The `store.InitDB()` function sets up the database connection with a singleton pattern using `sync.Once` to ensure it's only initialized once.
The project uses BadgerDB as the underlying storage engine. The `store.InitDB()` function sets up an in-memory database connection with a singleton pattern using `sync.Once` to ensure it's only initialized once.
### Data Models
The project defines strongly typed Go structs to enforce schema:
- `User`: Stores user information with ID, name, email, and password
- `Address`: Stores address information with reference to a user
- `Address`: Stores address information with reference to a user via UserID
### Data Access Pattern
@@ -46,6 +46,10 @@ addr, _ := store.AddAddressToUser(user.ID, "123 Main St", "Paris", "75001")
// List addresses for a user
addresses, _ := store.ListAddressesByUserID(user.ID)
// Update user data
user.Name = "Bobby"
store.UpdateUser(user)
```
### "Relational" Modeling
@@ -54,6 +58,7 @@ The project demonstrates relationship modeling between users and addresses:
1. Each address has a `UserID` field linking it to its owner
2. The `ListAddressesByUserID` function retrieves all addresses for a specific user
3. Updates to either entity maintain relationship integrity
### Data Consistency
@@ -61,8 +66,20 @@ The project implements data consistency features:
- Email uniqueness enforcement using an email-to-ID index
- Transaction support for atomic operations
- Error handling for constraint violations
## Dependencies
### Terminal Demo
- [BadgerDB v4](https://github.com/dgraph-io/badger) - Embedded key-value database
- [Google UUID](https://github.com/google/uuid) - For generating unique identifiers
The main.go file provides a demonstration that showcases:
1. User creation with validation
2. Address management (create, read, update)
3. Relationship handling (users have multiple addresses)
4. Constraint enforcement (unique emails)
5. Entity retrieval by ID and other fields
## Running the Demo
```bash
go run main.go
```
+3
View File
@@ -7,11 +7,14 @@ require (
github.com/dgraph-io/badger/v4 v4.7.0 // indirect
github.com/dgraph-io/ristretto/v2 v2.2.0 // indirect
github.com/dustin/go-humanize v1.0.1 // indirect
github.com/fatih/color v1.18.0 // indirect
github.com/go-logr/logr v1.4.2 // indirect
github.com/go-logr/stdr v1.2.2 // indirect
github.com/google/flatbuffers v25.2.10+incompatible // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/klauspost/compress v1.18.0 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
go.opentelemetry.io/auto/sdk v1.1.0 // indirect
go.opentelemetry.io/otel v1.35.0 // indirect
go.opentelemetry.io/otel/metric v1.35.0 // indirect
+9
View File
@@ -6,6 +6,8 @@ github.com/dgraph-io/ristretto/v2 v2.2.0 h1:bkY3XzJcXoMuELV8F+vS8kzNgicwQFAaGINA
github.com/dgraph-io/ristretto/v2 v2.2.0/go.mod h1:RZrm63UmcBAaYWC1DotLYBmTvgkrs0+XhBd7Npn7/zI=
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM=
github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU=
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY=
github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
@@ -17,6 +19,11 @@ github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo=
github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ=
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA=
go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A=
go.opentelemetry.io/otel v1.35.0 h1:xKWKPxrxB6OtMCbmMY021CqC45J+3Onta9MqjhnusiQ=
@@ -27,6 +34,8 @@ go.opentelemetry.io/otel/trace v1.35.0 h1:dPpEfJu1sDIqruz7BHFG3c7528f6ddfSWfFDVt
go.opentelemetry.io/otel/trace v1.35.0/go.mod h1:WUk7DtFp1Aw2MkvqGdwiXYDZZNvA/1J8o6xRXLrIkyc=
golang.org/x/net v0.38.0 h1:vRMAPTMaeGqVhG5QyLJHqNDwecKTomGeqbnfZyKlBI8=
golang.org/x/net v0.38.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik=
golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY=
+62 -12
View File
@@ -2,38 +2,88 @@ package main
import (
"fmt"
"strings"
"github.com/Floriansylvain/GoAntiSqlPOC/store"
"github.com/fatih/color"
)
func printSection(title string) {
color.Cyan("\n" + strings.Repeat("-", 40))
color.Cyan(" " + title)
}
func printSuccess(message string) {
color.Green("✅ " + message)
}
func printInfo(message string) {
color.White(" " + message)
}
func printWarning(message string) {
color.Yellow("⚠️ " + message)
}
func main() {
store.InitDB()
defer store.CloseDB()
// Create user
printSection("USER CREATION")
user, err := store.CreateUser("Bob", "bob@example.com", "hunter2")
if err != nil {
panic(err)
}
fmt.Printf("Created user: %+v\n", user)
printInfo(fmt.Sprintf("Created: %s | %s | %s", user.ID, user.Name, user.Email))
// Add addresses
_, _ = store.AddAddressToUser(user.ID, "123 Main St", "Paris", "75001")
_, _ = store.AddAddressToUser(user.ID, "456 Side Rd", "Lyon", "69000")
printSection("ADDRESS MANAGEMENT")
addr1, err := store.AddAddressToUser(user.ID, "123 Main St", "Paris", "75001")
if err != nil {
panic(err)
}
printInfo(fmt.Sprintf("Added: %s | %s, %s %s", addr1.ID, addr1.Line1, addr1.City, addr1.ZipCode))
addr2, err := store.AddAddressToUser(user.ID, "456 Side Rd", "Lyon", "69000")
if err != nil {
panic(err)
}
printInfo(fmt.Sprintf("Added: %s | %s, %s %s", addr2.ID, addr2.Line1, addr2.City, addr2.ZipCode))
// Retrieve addresses
addrs, err := store.ListAddressesByUserID(user.ID)
if err != nil {
panic(err)
}
fmt.Println("Addresses for", user.Name)
for _, a := range addrs {
fmt.Printf("- %s, %s %s\n", a.Line1, a.City, a.ZipCode)
printInfo(fmt.Sprintf("Retrieved: %d addresses for %s", len(addrs), user.Name))
printSection("DATA UPDATES")
if len(addrs) > 0 {
printInfo(fmt.Sprintf("Original city: %s → Changing to Marseille", addrs[0].City))
addrs[0].City = "Marseille"
err := store.UpdateAddress(addrs[0])
if err != nil {
panic(err)
}
}
// Attempt duplicate email
_, err = store.CreateUser("Alice", "bob@example.com", "oops")
addrs, _ = store.ListAddressesByUserID(user.ID)
printInfo(fmt.Sprintf("Address now: %s, %s %s", addrs[0].Line1, addrs[0].City, addrs[0].ZipCode))
printInfo(fmt.Sprintf("User name: %s → Changing to Bobby", user.Name))
user.Name = "Bobby"
if err := store.UpdateUser(user); err != nil {
panic(err)
}
newUser, _ := store.FindUserByID(user.ID)
printInfo(fmt.Sprintf("Name now: %s", newUser.Name))
printSection("SEARCH & CONSTRAINTS")
foundUser, _ := store.FindUserByEmail("bob@example.com")
printInfo(fmt.Sprintf("Found by email: %s | %s", foundUser.ID, foundUser.Name))
_, err = store.CreateUser("Alice", "bob@example.com", "password123")
if err != nil {
fmt.Println("Expected error:", err)
printSuccess("Email uniqueness enforced: " + err.Error())
} else {
printWarning("Failed email uniqueness check!")
}
}
+10
View File
@@ -57,3 +57,13 @@ func ListAddressesByUserID(userID string) ([]*Address, error) {
})
return addresses, err
}
func UpdateAddress(a *Address) error {
data, err := json.Marshal(a)
if err != nil {
return err
}
return GetDB().Update(func(txn *badger.Txn) error {
return txn.Set([]byte(addressPrefix+a.ID), data)
})
}
+1 -2
View File
@@ -10,12 +10,11 @@ import (
var (
db *badger.DB
once sync.Once
dbPath = "./data"
)
func InitDB() {
once.Do(func() {
opts := badger.DefaultOptions(dbPath).WithLogger(nil)
opts := badger.DefaultOptions("").WithInMemory(true).WithLogger(nil)
var err error
db, err = badger.Open(opts)
if err != nil {
+10 -1
View File
@@ -25,7 +25,6 @@ var (
)
func CreateUser(name, email, password string) (*User, error) {
// Enforce uniqueness
if _, err := findUserIDByEmail(email); err == nil {
return nil, ErrEmailExists
}
@@ -113,3 +112,13 @@ func ListUsers() ([]*User, error) {
})
return users, err
}
func UpdateUser(u *User) error {
data, err := json.Marshal(u)
if err != nil {
return err
}
return GetDB().Update(func(txn *badger.Txn) error {
return txn.Set([]byte(userPrefix+u.ID), data)
})
}