diff --git a/cmd/websrv/main.go b/cmd/websrv/main.go index cc5c3b8..1c90e28 100644 --- a/cmd/websrv/main.go +++ b/cmd/websrv/main.go @@ -1,12 +1,15 @@ package main import ( + "context" "encoding/json" "fmt" "io" "log" "net/http" "os" + "os/signal" + "syscall" "github.com/google/uuid" "gittea.marcokittel.de/elio/eliotools/datawriter/internal/api" @@ -46,9 +49,17 @@ const ( func main() { 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. nps.Autorelease() + if len(connectionString) == 0 { fmt.Println("Connectionstring fehlt!. Bsp.: :@tcp(127.0.0.1:3306)/elio?parseTime=true") return @@ -184,4 +195,5 @@ func main() { log.Printf("Easy Peasy: Die Party startet auf Port %s\n", port) log.Printf("Probiers mal damit: %s\n", curlhelp) log.Fatal(http.ListenAndServe(port, nil)) + } diff --git a/internal/database/database.go b/internal/database/database.go index a954460..058f39c 100644 --- a/internal/database/database.go +++ b/internal/database/database.go @@ -18,6 +18,7 @@ import ( type DatabaseWriter struct { log logger.Logger db *sql.DB + ctx context.Context } type DatabaseReader struct { @@ -26,8 +27,9 @@ type DatabaseReader struct { type UUID string -func NewDatabaseReader(connectionString string) *DatabaseReader { - return &DatabaseReader{*NewDatabaseWriter(connectionString)} +// Das ist ein Golang Design Flaw... Context so zu übergeben, bitte nicht hauen +func NewDatabaseReader(ctx context.Context, connectionString string) *DatabaseReader { + return &DatabaseReader{*NewDatabaseWriter(ctx, connectionString)} } func (d *DatabaseWriter) connectDB(connectionString string) (*sql.DB, error) { @@ -102,8 +104,9 @@ func (d *DatabaseWriter) createReservationTableIfNotExist() error { return err } -func NewDatabaseWriter(connectionString string) *DatabaseWriter { - db := DatabaseWriter{log: logger.NewMarcoLogger()} +// Brauche einen Context für Asyncrone Datenbank-Abfragen. Design Flaw by me :-() +func NewDatabaseWriter(ctx context.Context, connectionString string) *DatabaseWriter { + db := DatabaseWriter{log: logger.NewMarcoLogger(), ctx: ctx} sql, err := db.connectDB(connectionString) if err != nil { 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 { - _, 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) VALUES (?, ?, ?) 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 { - _, 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) VALUES (?, ?, ?, ?) 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) { + ctx, cancel := context.WithTimeout(d.ctx, 100*time.Millisecond) + defer cancel() stmt := ` 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 @@ -195,7 +204,7 @@ func (d *DatabaseReader) GetProductByProductIdDeliveryCountryAndState(prod_id, d 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 { return nil, err } @@ -212,10 +221,12 @@ func (d *DatabaseReader) GetProductByProductIdDeliveryCountryAndState(prod_id, d } func (d *DatabaseReader) GetReservationStateById(productId UUID) (string, error) { + ctx, cancel := context.WithTimeout(d.ctx, 100*time.Millisecond) + defer cancel() stmt := ` SELECT status FROM reservations WHERE id = ? ` - row, err := d.db.Query(stmt, productId) + row, err := d.db.QueryContext(ctx, stmt, productId) if err != nil { return "", err } @@ -230,10 +241,12 @@ func (d *DatabaseReader) GetReservationStateById(productId UUID) (string, error) } func (d *DatabaseReader) GetReservationItemsByGroupId(groupId UUID) (string, error) { + ctx, cancel := context.WithTimeout(d.ctx, 100*time.Millisecond) + defer cancel() stmt := ` SELECT status FROM reservations WHERE id = ? ` - row, err := d.db.Query(stmt, groupId) + row, err := d.db.QueryContext(ctx, stmt, groupId) if err != nil { return "", err } @@ -248,7 +261,9 @@ func (d *DatabaseReader) GetReservationItemsByGroupId(groupId UUID) (string, err } 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 { return err } @@ -256,7 +271,9 @@ func (d *DatabaseWriter) updateReservationState(Id 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 { return err } diff --git a/internal/database/database_test.go b/internal/database/database_test.go index 84c448f..d0c97b0 100644 --- a/internal/database/database_test.go +++ b/internal/database/database_test.go @@ -1,6 +1,7 @@ package database import ( + "context" "encoding/json" "fmt" "log" @@ -82,7 +83,8 @@ func Setup(dbr *DatabaseReader) error { } func TestDatabaseConnection(t *testing.T) { - var dbr = NewDatabaseReader(connectionString) + ctx := context.Background() + var dbr = NewDatabaseReader(ctx, connectionString) err := dbr.db.Ping() if err != nil { t.Errorf("Datenbankverbindung fehlgeschlagen! %s", err) @@ -90,7 +92,8 @@ func TestDatabaseConnection(t *testing.T) { } func TestSelectStatementNoDatasetsAvailable(t *testing.T) { - var dbr = NewDatabaseReader(connectionString) + ctx := context.Background() + var dbr = NewDatabaseReader(ctx, connectionString) err := Setup(dbr) if err != nil { t.Errorf("Setup failed! %s", err) @@ -102,7 +105,8 @@ func TestSelectStatementNoDatasetsAvailable(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", "") if err != nil { t.Errorf("Es sollten Datensätze auffinbdar sein! %s", err) @@ -121,7 +125,8 @@ func TestSelectStatement(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", "") if err != nil { t.Errorf("Es sollten Datensätze auffinbdar sein! %s", err) @@ -140,7 +145,8 @@ func TestInsertStatement(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", "") if err != nil { t.Errorf("Es sollten Datensätze auffinbdar sein! %s", err) @@ -166,7 +172,8 @@ func TestAbortReservationStatement(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", "") if err != nil { t.Errorf("Es sollten Datensätze auffinbdar sein! %s", err) @@ -192,7 +199,8 @@ func TestConfirmReservationStatement(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", "") if err != nil { 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) return } - ps := NewProductService(connectionString) + ctx := context.Background() + ps := NewProductService(ctx, connectionString) // groupId := uuid.New().String() op, err := ps.FetchData(&payload) 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) return } - ps := NewProductService(connectionString) + ctx := context.Background() + ps := NewProductService(ctx, connectionString) groupId := uuid.New().String() op, err := ps.FetchReservationData(&payload, UUID(groupId)) 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) return } - ps := NewProductService(connectionString) + ctx := context.Background() + ps := NewProductService(ctx, connectionString) op, err := ps.FetchData(&payload) if err != nil { 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) return } - ps := NewProductService(connectionString) + ctx := context.Background() + ps := NewProductService(ctx, connectionString) op, err := ps.FetchData(&payload) if err != nil { 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) return } - ps := NewProductService(connectionString) + ctx := context.Background() + ps := NewProductService(ctx, connectionString) op, err := ps.FetchData(&payload) if err != nil { 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) { //Löscht die Tabellen und erzeugt die Testdaten neu. //Das Verbessert den Überblick - var dbr = NewDatabaseReader(connectionString) + ctx := context.Background() + var dbr = NewDatabaseReader(ctx, connectionString) err := Setup(dbr) if err != nil { @@ -335,7 +349,7 @@ func TestOutgoingReservationInDatabase(t *testing.T) { return } groupId := uuid.New().String() - ps := NewProductService(connectionString) + ps := NewProductService(ctx, connectionString) op, err := ps.FetchReservationData(&payload, UUID(groupId)) if err != nil { t.Errorf("Das Datafetchen und umwandeln in eine Map muss sauber funktionieren! %s", err) diff --git a/internal/database/service.go b/internal/database/service.go index 49d4056..b11f064 100644 --- a/internal/database/service.go +++ b/internal/database/service.go @@ -4,6 +4,8 @@ import ( "fmt" "log" "time" + + "golang.org/x/net/context" ) type ProductService struct { @@ -49,8 +51,8 @@ type Context struct { State string `json:"state"` } -func NewProductService(connectionString string) *ProductService { - dbr := NewDatabaseReader(connectionString) +func NewProductService(ctx context.Context, connectionString string) *ProductService { + dbr := NewDatabaseReader(ctx, connectionString) p := ProductService{dbr: dbr} return &p }