diff --git a/cmd/dbos/init.go b/cmd/dbos/init.go index 59cb46bc..f454b8fc 100644 --- a/cmd/dbos/init.go +++ b/cmd/dbos/init.go @@ -25,7 +25,7 @@ func runInit(cmd *cobra.Command, args []string) error { if len(args) > 0 { projectName = args[0] } else { - projectName = "dbos-toolbox" + projectName = "dbos-go-starter" } // Check if directory already exists @@ -45,9 +45,10 @@ func runInit(cmd *cobra.Command, args []string) error { // Process and write each template file templates := map[string]string{ - "templates/dbos-toolbox/go.mod.tmpl": "go.mod", - "templates/dbos-toolbox/main.go.tmpl": "main.go", - "templates/dbos-toolbox/dbos-config.yaml.tmpl": "dbos-config.yaml", + "templates/dbos-go-starter/go.mod.tmpl": "go.mod", + "templates/dbos-go-starter/main.go.tmpl": "main.go", + "templates/dbos-go-starter/dbos-config.yaml.tmpl": "dbos-config.yaml", + "templates/dbos-go-starter/app.html": "html/app.html", } for tmplPath, outputFile := range templates { @@ -70,6 +71,9 @@ func runInit(cmd *cobra.Command, args []string) error { // Write output file outputPath := filepath.Join(projectName, outputFile) + if err := os.MkdirAll(filepath.Dir(outputPath), 0755); err != nil { + return fmt.Errorf("failed to create directory for %s: %w", outputFile, err) + } if err := os.WriteFile(outputPath, buf.Bytes(), 0644); err != nil { return fmt.Errorf("failed to write %s: %w", outputFile, err) } @@ -79,7 +83,7 @@ func runInit(cmd *cobra.Command, args []string) error { fmt.Println("To get started:") fmt.Printf(" cd %s\n", projectName) fmt.Println(" go mod tidy") - fmt.Println(" export DBOS_SYSTEM_DATABASE_URL=") + fmt.Println(" export DBOS_SYSTEM_DATABASE_URL=\"postgres://:@:/\"") fmt.Println(" go run main.go") return nil diff --git a/cmd/dbos/templates.go b/cmd/dbos/templates.go index c54f62a3..3678cc08 100644 --- a/cmd/dbos/templates.go +++ b/cmd/dbos/templates.go @@ -4,5 +4,5 @@ import ( "embed" ) -//go:embed templates/dbos-toolbox/* -var templateFS embed.FS \ No newline at end of file +//go:embed templates/dbos-go-starter/* +var templateFS embed.FS diff --git a/cmd/dbos/templates/dbos-go-starter/app.html b/cmd/dbos/templates/dbos-go-starter/app.html new file mode 100644 index 00000000..309a3534 --- /dev/null +++ b/cmd/dbos/templates/dbos-go-starter/app.html @@ -0,0 +1,340 @@ + + + + Welcome to DBOS! + + + + + + +
+

Welcome to DBOS!

+ +

DBOS helps you build applications that are resilient to any failure.

+ +

+ Simply add DBOS to your existing Go program by registering ordinary functions as workflows and steps. These durably execute your program, persisting its state to a Postgres database. If your + program is ever interrupted or crashes, DBOS uses this saved state to recover it from the last completed step. +

+

+ Try it: Launch a durable workflow, then crash the application. When your application restarts, your workflow will always recover from the last + completed step! +

+
+ + +
+
+ +
+
+
func stepOne(ctx context.Context) (string, error) { time.Sleep(5 * time.Second) fmt.Println("Step one completed!") return "Step 1 completed", nil}
func stepTwo(ctx context.Context) (string, error) { time.Sleep(5 * time.Second) fmt.Println("Step two completed!") return "Step 2 completed", nil}
func stepThree(ctx context.Context) (string, error) { time.Sleep(5 * time.Second) fmt.Println("Step three completed!") return "Step 3 completed", nil}
func ExampleWorkflow(ctx dbos.DBOSContext) { dbos.RunAsStep(ctx, func(stepCtx context.Context) (string, error) { return stepOne(stepCtx) }) dbos.RunAsStep(ctx, func(stepCtx context.Context) (string, error) { return stepTwo(stepCtx) }) dbos.RunAsStep(ctx, func(stepCtx context.Context) (string, error) { return stepThree(stepCtx) })}
+
+
+ + +
+
+ + +
+
+
+
+
+
+
+
+
+ + +
+

+ To see it's really working, check your app's logs. When you crash your app, its process is forcibly exited. + When the process restarts, it recovers any active workflows from their last completed step + (Recovered pending workflows...), + then runs the recovered workflows (Step completed...). +

+

+ Check out the + programming guide + to learn how to build with DBOS! +

+
+ + + + \ No newline at end of file diff --git a/cmd/dbos/templates/dbos-toolbox/dbos-config.yaml.tmpl b/cmd/dbos/templates/dbos-go-starter/dbos-config.yaml.tmpl similarity index 100% rename from cmd/dbos/templates/dbos-toolbox/dbos-config.yaml.tmpl rename to cmd/dbos/templates/dbos-go-starter/dbos-config.yaml.tmpl diff --git a/cmd/dbos/templates/dbos-toolbox/go.mod.tmpl b/cmd/dbos/templates/dbos-go-starter/go.mod.tmpl similarity index 100% rename from cmd/dbos/templates/dbos-toolbox/go.mod.tmpl rename to cmd/dbos/templates/dbos-go-starter/go.mod.tmpl diff --git a/cmd/dbos/templates/dbos-go-starter/main.go.tmpl b/cmd/dbos/templates/dbos-go-starter/main.go.tmpl new file mode 100644 index 00000000..9b154d6e --- /dev/null +++ b/cmd/dbos/templates/dbos-go-starter/main.go.tmpl @@ -0,0 +1,155 @@ +package main + +import ( + "context" + "fmt" + "net/http" + "os" + "time" + + "github.com/dbos-inc/dbos-transact-golang/dbos" + "github.com/gin-gonic/gin" +) + +const STEPS_EVENT = "steps_event" + +var dbosCtx dbos.DBOSContext + +/*****************************/ +/**** WORKFLOWS AND STEPS ****/ +/*****************************/ + +func ExampleWorkflow(ctx dbos.DBOSContext, _ string) (string, error) { + _, err := dbos.RunAsStep(ctx, func(stepCtx context.Context) (string, error) { + return stepOne(stepCtx) + }) + if err != nil { + return "", err + } + err = dbos.SetEvent(ctx, STEPS_EVENT, 1) + if err != nil { + return "", err + } + _, err = dbos.RunAsStep(ctx, func(stepCtx context.Context) (string, error) { + return stepTwo(stepCtx) + }) + if err != nil { + return "", err + } + err = dbos.SetEvent(ctx, STEPS_EVENT, 2) + if err != nil { + return "", err + } + _, err = dbos.RunAsStep(ctx, func(stepCtx context.Context) (string, error) { + return stepThree(stepCtx) + }) + if err != nil { + return "", err + } + err = dbos.SetEvent(ctx, STEPS_EVENT, 3) + if err != nil { + return "", err + } + return "Workflow completed", nil +} + +func stepOne(ctx context.Context) (string, error) { + time.Sleep(5 * time.Second) + fmt.Println("Step one completed!") + return "Step 1 completed", nil +} + +func stepTwo(ctx context.Context) (string, error) { + time.Sleep(5 * time.Second) + fmt.Println("Step two completed!") + return "Step 2 completed", nil +} + +func stepThree(ctx context.Context) (string, error) { + time.Sleep(5 * time.Second) + fmt.Println("Step three completed!") + return "Step 3 completed", nil +} + +/*****************************/ +/**** Main Function **********/ +/*****************************/ + +func main() { + // Create DBOS context + var err error + dbosCtx, err = dbos.NewDBOSContext(context.Background(), dbos.Config{ + DatabaseURL: os.Getenv("DBOS_SYSTEM_DATABASE_URL"), + AppName: "{{.ProjectName}}", + AdminServer: true, + }) + if err != nil { + panic(err) + } + + // Register workflows + dbos.RegisterWorkflow(dbosCtx, ExampleWorkflow) + + // Launch DBOS + err = dbosCtx.Launch() + if err != nil { + panic(err) + } + defer dbosCtx.Shutdown(10 * time.Second) + + // Initialize Gin router + router := gin.Default() + + // HTTP Handlers + router.StaticFile("/", "./html/app.html") + router.GET("/workflow/:taskid", workflowHandler) + router.GET("/last_step/:taskid", lastStepHandler) + router.POST("/crash", crashHandler) + + fmt.Println("Server starting on http://localhost:8080") + err = router.Run(":8080") + if err != nil { + fmt.Printf("Error starting server: %s\n", err) + } +} + +/*****************************/ +/**** HTTP HANDLERS **********/ +/*****************************/ + +func workflowHandler(c *gin.Context) { + taskID := c.Param("taskid") + + if taskID == "" { + c.JSON(http.StatusBadRequest, gin.H{"error": "Task ID is required"}) + return + } + + _, err := dbos.RunWorkflow(dbosCtx, ExampleWorkflow, "", dbos.WithWorkflowID(taskID)) + if err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + return + } +} + +func lastStepHandler(c *gin.Context) { + taskID := c.Param("taskid") + + if taskID == "" { + c.JSON(http.StatusBadRequest, gin.H{"error": "Task ID is required"}) + return + } + + step, err := dbos.GetEvent[int](dbosCtx, taskID, STEPS_EVENT, 0) + if err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + return + } + + c.String(http.StatusOK, fmt.Sprintf("%d", step)) +} + +// This endpoint crashes the application. For demonstration purposes only :) +func crashHandler(c *gin.Context) { + os.Exit(1) +} \ No newline at end of file diff --git a/cmd/dbos/templates/dbos-toolbox/main.go.tmpl b/cmd/dbos/templates/dbos-toolbox/main.go.tmpl deleted file mode 100644 index 362d1895..00000000 --- a/cmd/dbos/templates/dbos-toolbox/main.go.tmpl +++ /dev/null @@ -1,232 +0,0 @@ -package main - -import ( - "context" - "fmt" - "net/http" - "os" - "time" - - "github.com/dbos-inc/dbos-transact-golang/dbos" -) - -// Configuration structure -type Config struct { - Name string `yaml:"name"` - Language string `yaml:"language"` - DatabaseURL string `yaml:"database_url"` -} - -// Global variables -var dbosCtx dbos.DBOSContext - -/*****************************/ -/**** WORKFLOWS AND STEPS ****/ -/*****************************/ - -func ExampleWorkflow(ctx dbos.DBOSContext, _ string) (string, error) { - _, err := dbos.RunAsStep(ctx, func(stepCtx context.Context) (string, error) { - return stepOne(stepCtx) - }) - if err != nil { - return "", err - } - return dbos.RunAsStep(ctx, func(stepCtx context.Context) (string, error) { - return stepTwo(stepCtx) - }) -} - -func stepOne(ctx context.Context) (string, error) { - fmt.Println("Step one completed!") - return "Step 1 completed", nil -} - -func stepTwo(ctx context.Context) (string, error) { - fmt.Println("Step two completed!") - return "Step 2 completed - Workflow finished successfully", nil -} - -/*****************************/ -/**** QUEUES *****************/ -/*****************************/ - -func QueuedStepWorkflow(ctx dbos.DBOSContext, i int) (int, error) { - dbos.Sleep(ctx, 5*time.Second) - fmt.Printf("Step %d completed!\n", i) - return i, nil -} - -func QueueWorkflow(ctx dbos.DBOSContext, _ string) (string, error) { - fmt.Println("Enqueueing steps") - handles := make([]dbos.WorkflowHandle[int], 10) - for i := range 10 { - handle, err := dbos.RunWorkflow(ctx, QueuedStepWorkflow, i, dbos.WithQueue("example-queue")) - if err != nil { - return "", fmt.Errorf("failed to enqueue step %d: %w", i, err) - } - handles[i] = handle - } - results := make([]int, 10) - for i, handle := range handles { - result, err := handle.GetResult() - if err != nil { - return "", fmt.Errorf("failed to get result for step %d: %w", i, err) - } - results[i] = result - } - fmt.Printf("Successfully completed %d steps\n", len(results)) - return fmt.Sprintf("Successfully completed %d steps", len(results)), nil -} - -/*****************************/ -/**** SCHEDULED WORKFLOWS ****/ -/*****************************/ - -func ScheduledWorkflow(ctx dbos.DBOSContext, scheduledTime time.Time) (string, error) { - fmt.Printf("I am a scheduled workflow scheduled at %v and running at %v\n", scheduledTime, time.Now()) - return "", nil -} - -func main() { - // Create DBOS context - var err error - dbosCtx, err = dbos.NewDBOSContext(context.Background(), dbos.Config{ - DatabaseURL: os.Getenv("DBOS_SYSTEM_DATABASE_URL"), - AppName: "{{.ProjectName}}", - AdminServer: true, - }) - if err != nil { - panic(err) - } - - // Register workflows - dbos.RegisterWorkflow(dbosCtx, ExampleWorkflow) - dbos.RegisterWorkflow(dbosCtx, QueueWorkflow) - dbos.RegisterWorkflow(dbosCtx, QueuedStepWorkflow) - dbos.RegisterWorkflow(dbosCtx, ScheduledWorkflow, dbos.WithSchedule("*/15 * * * * *")) - - // Create queue - dbos.NewWorkflowQueue(dbosCtx, "example-queue") - - // Launch DBOS - err = dbosCtx.Launch() - if err != nil { - panic(err) - } - defer dbosCtx.Shutdown(10 * time.Second) - - // HTTP Handlers - http.HandleFunc("/", homepageHandler) - http.HandleFunc("/workflow", workflowHandler) - http.HandleFunc("/queue", queueHandler) - - fmt.Println("Server starting on http://localhost:8080") - err = http.ListenAndServe(":8080", nil) - if err != nil { - fmt.Printf("Error starting server: %s\n", err) - } -} - -/*****************************/ -/**** HTTP HANDLERS **********/ -/*****************************/ - -func workflowHandler(w http.ResponseWriter, r *http.Request) { - handle, err := dbos.RunWorkflow(dbosCtx, ExampleWorkflow, "") - if err != nil { - http.Error(w, fmt.Sprintf("Error: %s", err), http.StatusInternalServerError) - return - } - res, err := handle.GetResult() - if err != nil { - http.Error(w, fmt.Sprintf("Error: %s", err), http.StatusInternalServerError) - return - } - fmt.Fprintf(w, "Workflow result: %s", res) -} - -func queueHandler(w http.ResponseWriter, r *http.Request) { - handle, err := dbos.RunWorkflow(dbosCtx, QueueWorkflow, "") - if err != nil { - http.Error(w, fmt.Sprintf("Error: %s", err), http.StatusInternalServerError) - return - } - res, err := handle.GetResult() - if err != nil { - http.Error(w, fmt.Sprintf("Error: %s", err), http.StatusInternalServerError) - return - } - fmt.Fprintf(w, "Workflow result: %s", res) -} - -func homepageHandler(w http.ResponseWriter, r *http.Request) { - w.Header().Set("Content-Type", "text/html") - fmt.Fprint(w, ` - - - - - - - - DBOS Toolbox - - -
-
-

Welcome to the DBOS Toolbox!

- -

- This app contains example code for many DBOS features. You can use it as a template when starting a new DBOS app—start by editing main.go. -

- -

- Each endpoint launches a new workflow—view the app logs to see them run. -

- -
-
- Workflows: -
-
- Queues: -
-
- -
-

To get started developing locally:

-
    -
  • - - 1 - - go get github.com/dbos-inc/dbos-transact-go -
  • -
  • - - 2 - - git clone https://github.com/dbos-inc/dbos-demo-apps -
  • -
  • - - 3 - - Edit main.go to start building! -
  • -
-
- -

- Check out the - - programming guide - - to learn how to build with DBOS! -

-
-
- - - `) -} \ No newline at end of file