Datenbank Calls Asyn gemacht. Problem: OOP Style für Context Passthrough benutzt. Dazu sterben meine Goroutinen nicht bei STRG+C. Scheiße!

This commit is contained in:
Marco Kittel 2025-07-19 16:44:37 +02:00
parent fee5dd1474
commit 9732096f53
4 changed files with 73 additions and 28 deletions

View File

@ -1,12 +1,15 @@
package main package main
import ( import (
"context"
"encoding/json" "encoding/json"
"fmt" "fmt"
"io" "io"
"log" "log"
"net/http" "net/http"
"os" "os"
"os/signal"
"syscall"
"github.com/google/uuid" "github.com/google/uuid"
"gittea.marcokittel.de/elio/eliotools/datawriter/internal/api" "gittea.marcokittel.de/elio/eliotools/datawriter/internal/api"
@ -46,9 +49,17 @@ const (
func main() { func main() {
connectionString := os.Getenv("CONNECTIONSTRING") connectionString := os.Getenv("CONNECTIONSTRING")
nps := database.NewProductService(connectionString) ctx, cancel := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM)
defer cancel()
// Brauche Context für Asyncrone Calls. Darum nutze ich DI. Ist ein Golang Antipattern :-(
// Muss noch mehr Lernen um idiomatisch zu werden
nps := database.NewProductService(ctx, connectionString)
//Hier prüfe ich nach alten Registrierungen und gebe Sie frei. //Hier prüfe ich nach alten Registrierungen und gebe Sie frei.
nps.Autorelease() nps.Autorelease()
if len(connectionString) == 0 { if len(connectionString) == 0 {
fmt.Println("Connectionstring fehlt!. Bsp.: <user>:<passwort>@tcp(127.0.0.1:3306)/elio?parseTime=true") fmt.Println("Connectionstring fehlt!. Bsp.: <user>:<passwort>@tcp(127.0.0.1:3306)/elio?parseTime=true")
return return
@ -184,4 +195,5 @@ func main() {
log.Printf("Easy Peasy: Die Party startet auf Port %s\n", port) log.Printf("Easy Peasy: Die Party startet auf Port %s\n", port)
log.Printf("Probiers mal damit: %s\n", curlhelp) log.Printf("Probiers mal damit: %s\n", curlhelp)
log.Fatal(http.ListenAndServe(port, nil)) log.Fatal(http.ListenAndServe(port, nil))
} }

View File

@ -18,6 +18,7 @@ import (
type DatabaseWriter struct { type DatabaseWriter struct {
log logger.Logger log logger.Logger
db *sql.DB db *sql.DB
ctx context.Context
} }
type DatabaseReader struct { type DatabaseReader struct {
@ -26,8 +27,9 @@ type DatabaseReader struct {
type UUID string type UUID string
func NewDatabaseReader(connectionString string) *DatabaseReader { // Das ist ein Golang Design Flaw... Context so zu übergeben, bitte nicht hauen
return &DatabaseReader{*NewDatabaseWriter(connectionString)} func NewDatabaseReader(ctx context.Context, connectionString string) *DatabaseReader {
return &DatabaseReader{*NewDatabaseWriter(ctx, connectionString)}
} }
func (d *DatabaseWriter) connectDB(connectionString string) (*sql.DB, error) { func (d *DatabaseWriter) connectDB(connectionString string) (*sql.DB, error) {
@ -102,8 +104,9 @@ func (d *DatabaseWriter) createReservationTableIfNotExist() error {
return err return err
} }
func NewDatabaseWriter(connectionString string) *DatabaseWriter { // Brauche einen Context für Asyncrone Datenbank-Abfragen. Design Flaw by me :-()
db := DatabaseWriter{log: logger.NewMarcoLogger()} func NewDatabaseWriter(ctx context.Context, connectionString string) *DatabaseWriter {
db := DatabaseWriter{log: logger.NewMarcoLogger(), ctx: ctx}
sql, err := db.connectDB(connectionString) sql, err := db.connectDB(connectionString)
if err != nil { if err != nil {
fmt.Printf("Datenbank nicht gefunden. %s", err) fmt.Printf("Datenbank nicht gefunden. %s", err)
@ -139,7 +142,9 @@ func (d *DatabaseWriter) ReleaseReservierungenAfterOneDay() error {
} }
func (d *DatabaseWriter) UpdateOrInsertWarehouseProduct(warehouse string, productID string, amount int) error { func (d *DatabaseWriter) UpdateOrInsertWarehouseProduct(warehouse string, productID string, amount int) error {
_, err := d.db.Exec(` ctx, cancel := context.WithTimeout(d.ctx, 100*time.Millisecond)
defer cancel()
_, err := d.db.ExecContext(ctx, `
INSERT INTO warehouseproducts (warehouse, productid, amount) INSERT INTO warehouseproducts (warehouse, productid, amount)
VALUES (?, ?, ?) VALUES (?, ?, ?)
ON DUPLICATE KEY UPDATE amount = VALUES(amount)`, ON DUPLICATE KEY UPDATE amount = VALUES(amount)`,
@ -152,7 +157,9 @@ func (d *DatabaseWriter) UpdateOrInsertWarehouseProduct(warehouse string, produc
} }
func (d *DatabaseWriter) UpdateOrInsertDelivery(fromcountry string, tocountry string, state string, delivery int) error { func (d *DatabaseWriter) UpdateOrInsertDelivery(fromcountry string, tocountry string, state string, delivery int) error {
_, err := d.db.Exec(` ctx, cancel := context.WithTimeout(d.ctx, 100*time.Millisecond)
defer cancel()
_, err := d.db.ExecContext(ctx, `
INSERT INTO deliverytimes (fromcountry, tocountry, state, delivery) INSERT INTO deliverytimes (fromcountry, tocountry, state, delivery)
VALUES (?, ?, ?, ?) VALUES (?, ?, ?, ?)
ON DUPLICATE KEY UPDATE ON DUPLICATE KEY UPDATE
@ -177,6 +184,8 @@ type ProductDelivery struct {
} }
func (d *DatabaseReader) GetProductByProductIdDeliveryCountryAndState(prod_id, delivery_country, delivery_country_state string) ([]ProductDelivery, error) { func (d *DatabaseReader) GetProductByProductIdDeliveryCountryAndState(prod_id, delivery_country, delivery_country_state string) ([]ProductDelivery, error) {
ctx, cancel := context.WithTimeout(d.ctx, 100*time.Millisecond)
defer cancel()
stmt := ` stmt := `
SELECT id, warehouse, (amount - reserviert) amount, delivery, deliveryId FROM( SELECT id, warehouse, (amount - reserviert) amount, delivery, deliveryId FROM(
SELECT whp.id, warehouse, whp.amount, sum(coalesce(r.amount,0) )reserviert, d.delivery, d.id deliveryId SELECT whp.id, warehouse, whp.amount, sum(coalesce(r.amount,0) )reserviert, d.delivery, d.id deliveryId
@ -195,7 +204,7 @@ func (d *DatabaseReader) GetProductByProductIdDeliveryCountryAndState(prod_id, d
having amount > 0 having amount > 0
` `
rows, err := d.db.Query(stmt, prod_id, delivery_country, delivery_country_state) rows, err := d.db.QueryContext(ctx, stmt, prod_id, delivery_country, delivery_country_state)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -212,10 +221,12 @@ func (d *DatabaseReader) GetProductByProductIdDeliveryCountryAndState(prod_id, d
} }
func (d *DatabaseReader) GetReservationStateById(productId UUID) (string, error) { func (d *DatabaseReader) GetReservationStateById(productId UUID) (string, error) {
ctx, cancel := context.WithTimeout(d.ctx, 100*time.Millisecond)
defer cancel()
stmt := ` stmt := `
SELECT status FROM reservations WHERE id = ? SELECT status FROM reservations WHERE id = ?
` `
row, err := d.db.Query(stmt, productId) row, err := d.db.QueryContext(ctx, stmt, productId)
if err != nil { if err != nil {
return "", err return "", err
} }
@ -230,10 +241,12 @@ func (d *DatabaseReader) GetReservationStateById(productId UUID) (string, error)
} }
func (d *DatabaseReader) GetReservationItemsByGroupId(groupId UUID) (string, error) { func (d *DatabaseReader) GetReservationItemsByGroupId(groupId UUID) (string, error) {
ctx, cancel := context.WithTimeout(d.ctx, 100*time.Millisecond)
defer cancel()
stmt := ` stmt := `
SELECT status FROM reservations WHERE id = ? SELECT status FROM reservations WHERE id = ?
` `
row, err := d.db.Query(stmt, groupId) row, err := d.db.QueryContext(ctx, stmt, groupId)
if err != nil { if err != nil {
return "", err return "", err
} }
@ -248,7 +261,9 @@ func (d *DatabaseReader) GetReservationItemsByGroupId(groupId UUID) (string, err
} }
func (d *DatabaseWriter) updateReservationState(Id UUID, status string) error { func (d *DatabaseWriter) updateReservationState(Id UUID, status string) error {
_, err := d.db.Exec("UPDATE reservations SET Status=? WHERE id=?", status, Id) ctx, cancel := context.WithTimeout(d.ctx, 100*time.Millisecond)
defer cancel()
_, err := d.db.ExecContext(ctx, "UPDATE reservations SET Status=? WHERE id=?", status, Id)
if err != nil { if err != nil {
return err return err
} }
@ -256,7 +271,9 @@ func (d *DatabaseWriter) updateReservationState(Id UUID, status string) error {
} }
func (d *DatabaseWriter) updateReservationStateByGroupId(groupId UUID, status string) error { func (d *DatabaseWriter) updateReservationStateByGroupId(groupId UUID, status string) error {
_, err := d.db.Exec("UPDATE reservations SET Status=? WHERE reservationGroupId=?", status, groupId) ctx, cancel := context.WithTimeout(d.ctx, 100*time.Millisecond)
defer cancel()
_, err := d.db.ExecContext(ctx, "UPDATE reservations SET Status=? WHERE reservationGroupId=?", status, groupId)
if err != nil { if err != nil {
return err return err
} }

View File

@ -1,6 +1,7 @@
package database package database
import ( import (
"context"
"encoding/json" "encoding/json"
"fmt" "fmt"
"log" "log"
@ -82,7 +83,8 @@ func Setup(dbr *DatabaseReader) error {
} }
func TestDatabaseConnection(t *testing.T) { func TestDatabaseConnection(t *testing.T) {
var dbr = NewDatabaseReader(connectionString) ctx := context.Background()
var dbr = NewDatabaseReader(ctx, connectionString)
err := dbr.db.Ping() err := dbr.db.Ping()
if err != nil { if err != nil {
t.Errorf("Datenbankverbindung fehlgeschlagen! %s", err) t.Errorf("Datenbankverbindung fehlgeschlagen! %s", err)
@ -90,7 +92,8 @@ func TestDatabaseConnection(t *testing.T) {
} }
func TestSelectStatementNoDatasetsAvailable(t *testing.T) { func TestSelectStatementNoDatasetsAvailable(t *testing.T) {
var dbr = NewDatabaseReader(connectionString) ctx := context.Background()
var dbr = NewDatabaseReader(ctx, connectionString)
err := Setup(dbr) err := Setup(dbr)
if err != nil { if err != nil {
t.Errorf("Setup failed! %s", err) t.Errorf("Setup failed! %s", err)
@ -102,7 +105,8 @@ func TestSelectStatementNoDatasetsAvailable(t *testing.T) {
} }
func TestSelectStatement(t *testing.T) { func TestSelectStatement(t *testing.T) {
var dbr = NewDatabaseReader(connectionString) ctx := context.Background()
var dbr = NewDatabaseReader(ctx, connectionString)
data, err := dbr.GetProductByProductIdDeliveryCountryAndState("A6053", "EU", "") data, err := dbr.GetProductByProductIdDeliveryCountryAndState("A6053", "EU", "")
if err != nil { if err != nil {
t.Errorf("Es sollten Datensätze auffinbdar sein! %s", err) t.Errorf("Es sollten Datensätze auffinbdar sein! %s", err)
@ -121,7 +125,8 @@ func TestSelectStatement(t *testing.T) {
} }
func TestInsertStatement(t *testing.T) { func TestInsertStatement(t *testing.T) {
var dbr = NewDatabaseReader(connectionString) ctx := context.Background()
var dbr = NewDatabaseReader(ctx, connectionString)
data, err := dbr.GetProductByProductIdDeliveryCountryAndState("A6053", "EU", "") data, err := dbr.GetProductByProductIdDeliveryCountryAndState("A6053", "EU", "")
if err != nil { if err != nil {
t.Errorf("Es sollten Datensätze auffinbdar sein! %s", err) t.Errorf("Es sollten Datensätze auffinbdar sein! %s", err)
@ -140,7 +145,8 @@ func TestInsertStatement(t *testing.T) {
} }
func TestAbortReservationStatement(t *testing.T) { func TestAbortReservationStatement(t *testing.T) {
var dbr = NewDatabaseReader(connectionString) ctx := context.Background()
var dbr = NewDatabaseReader(ctx, connectionString)
data, err := dbr.GetProductByProductIdDeliveryCountryAndState("A6053", "EU", "") data, err := dbr.GetProductByProductIdDeliveryCountryAndState("A6053", "EU", "")
if err != nil { if err != nil {
t.Errorf("Es sollten Datensätze auffinbdar sein! %s", err) t.Errorf("Es sollten Datensätze auffinbdar sein! %s", err)
@ -166,7 +172,8 @@ func TestAbortReservationStatement(t *testing.T) {
} }
func TestConfirmReservationStatement(t *testing.T) { func TestConfirmReservationStatement(t *testing.T) {
var dbr = NewDatabaseReader(connectionString) ctx := context.Background()
var dbr = NewDatabaseReader(ctx, connectionString)
data, err := dbr.GetProductByProductIdDeliveryCountryAndState("A6053", "EU", "") data, err := dbr.GetProductByProductIdDeliveryCountryAndState("A6053", "EU", "")
if err != nil { if err != nil {
t.Errorf("Es sollten Datensätze auffinbdar sein! %s", err) t.Errorf("Es sollten Datensätze auffinbdar sein! %s", err)
@ -192,7 +199,8 @@ func TestConfirmReservationStatement(t *testing.T) {
} }
func TestReleasedReservationStatement(t *testing.T) { func TestReleasedReservationStatement(t *testing.T) {
var dbr = NewDatabaseReader(connectionString) ctx := context.Background()
var dbr = NewDatabaseReader(ctx, connectionString)
data, err := dbr.GetProductByProductIdDeliveryCountryAndState("A6053", "EU", "") data, err := dbr.GetProductByProductIdDeliveryCountryAndState("A6053", "EU", "")
if err != nil { if err != nil {
t.Errorf("Es sollten Datensätze auffinbdar sein! %s", err) t.Errorf("Es sollten Datensätze auffinbdar sein! %s", err)
@ -225,7 +233,8 @@ func TestFetchReservationData(t *testing.T) {
t.Errorf("Dieser Fehler beim Konvertieren der JSON Payload sollte nicht passieren! \n%s", err) t.Errorf("Dieser Fehler beim Konvertieren der JSON Payload sollte nicht passieren! \n%s", err)
return return
} }
ps := NewProductService(connectionString) ctx := context.Background()
ps := NewProductService(ctx, connectionString)
// groupId := uuid.New().String() // groupId := uuid.New().String()
op, err := ps.FetchData(&payload) op, err := ps.FetchData(&payload)
log.Println(op) log.Println(op)
@ -240,7 +249,8 @@ func TestFetchDataReservation(t *testing.T) {
t.Errorf("Dieser Fehler beim Konvertieren der JSON Payload sollte nicht passieren! \n%s", err) t.Errorf("Dieser Fehler beim Konvertieren der JSON Payload sollte nicht passieren! \n%s", err)
return return
} }
ps := NewProductService(connectionString) ctx := context.Background()
ps := NewProductService(ctx, connectionString)
groupId := uuid.New().String() groupId := uuid.New().String()
op, err := ps.FetchReservationData(&payload, UUID(groupId)) op, err := ps.FetchReservationData(&payload, UUID(groupId))
if err != nil { if err != nil {
@ -261,7 +271,8 @@ func TestFetchDataReservationVarition2(t *testing.T) {
t.Errorf("Dieser Fehler beim Konvertieren der JSON Payload sollte nicht passieren! \n%s", err) t.Errorf("Dieser Fehler beim Konvertieren der JSON Payload sollte nicht passieren! \n%s", err)
return return
} }
ps := NewProductService(connectionString) ctx := context.Background()
ps := NewProductService(ctx, connectionString)
op, err := ps.FetchData(&payload) op, err := ps.FetchData(&payload)
if err != nil { if err != nil {
t.Errorf("Das Datafetchen und umwandeln in eine Map muss sauber funktionieren! %s", err) t.Errorf("Das Datafetchen und umwandeln in eine Map muss sauber funktionieren! %s", err)
@ -281,7 +292,8 @@ func TestFetchDataReservationVarition3(t *testing.T) {
t.Errorf("Dieser Fehler beim Konvertieren der JSON Payload sollte nicht passieren! \n%s", err) t.Errorf("Dieser Fehler beim Konvertieren der JSON Payload sollte nicht passieren! \n%s", err)
return return
} }
ps := NewProductService(connectionString) ctx := context.Background()
ps := NewProductService(ctx, connectionString)
op, err := ps.FetchData(&payload) op, err := ps.FetchData(&payload)
if err != nil { if err != nil {
t.Errorf("Das Datafetchen und umwandeln in eine Map muss sauber funktionieren! %s", err) t.Errorf("Das Datafetchen und umwandeln in eine Map muss sauber funktionieren! %s", err)
@ -301,7 +313,8 @@ func TestOutgoingJsonString(t *testing.T) {
t.Errorf("Dieser Fehler beim Konvertieren der JSON Payload sollte nicht passieren! \n%s", err) t.Errorf("Dieser Fehler beim Konvertieren der JSON Payload sollte nicht passieren! \n%s", err)
return return
} }
ps := NewProductService(connectionString) ctx := context.Background()
ps := NewProductService(ctx, connectionString)
op, err := ps.FetchData(&payload) op, err := ps.FetchData(&payload)
if err != nil { if err != nil {
t.Errorf("Das Datafetchen und umwandeln in eine Map muss sauber funktionieren! %s", err) t.Errorf("Das Datafetchen und umwandeln in eine Map muss sauber funktionieren! %s", err)
@ -320,7 +333,8 @@ func TestOutgoingJsonString(t *testing.T) {
func TestOutgoingReservationInDatabase(t *testing.T) { func TestOutgoingReservationInDatabase(t *testing.T) {
//Löscht die Tabellen und erzeugt die Testdaten neu. //Löscht die Tabellen und erzeugt die Testdaten neu.
//Das Verbessert den Überblick //Das Verbessert den Überblick
var dbr = NewDatabaseReader(connectionString) ctx := context.Background()
var dbr = NewDatabaseReader(ctx, connectionString)
err := Setup(dbr) err := Setup(dbr)
if err != nil { if err != nil {
@ -335,7 +349,7 @@ func TestOutgoingReservationInDatabase(t *testing.T) {
return return
} }
groupId := uuid.New().String() groupId := uuid.New().String()
ps := NewProductService(connectionString) ps := NewProductService(ctx, connectionString)
op, err := ps.FetchReservationData(&payload, UUID(groupId)) op, err := ps.FetchReservationData(&payload, UUID(groupId))
if err != nil { if err != nil {
t.Errorf("Das Datafetchen und umwandeln in eine Map muss sauber funktionieren! %s", err) t.Errorf("Das Datafetchen und umwandeln in eine Map muss sauber funktionieren! %s", err)

View File

@ -4,6 +4,8 @@ import (
"fmt" "fmt"
"log" "log"
"time" "time"
"golang.org/x/net/context"
) )
type ProductService struct { type ProductService struct {
@ -49,8 +51,8 @@ type Context struct {
State string `json:"state"` State string `json:"state"`
} }
func NewProductService(connectionString string) *ProductService { func NewProductService(ctx context.Context, connectionString string) *ProductService {
dbr := NewDatabaseReader(connectionString) dbr := NewDatabaseReader(ctx, connectionString)
p := ProductService{dbr: dbr} p := ProductService{dbr: dbr}
return &p return &p
} }