Commit 02735b0
Changed files (3)
cmd
web
docs
cmd/web/handlers.go
@@ -1,12 +1,31 @@
package main
import (
+ "fmt"
"html/template"
"log"
"net/http"
+ "os"
"regexp"
)
+// Interfaces for future service dependencies
+// These will be implemented when we add database and email functionality
+
+// Database represents the database service interface
+type Database interface {
+ // Future database methods will be defined here
+ // Example: GetReminder(id string) (*Reminder, error)
+ // Example: SaveReminder(reminder *Reminder) error
+}
+
+// Mailer represents the email service interface
+type Mailer interface {
+ // Future email methods will be defined here
+ // Example: SendReminder(email, productURL string) error
+ // Example: SendConfirmation(email, token string) error
+}
+
// PageData holds common data passed to templates
type PageData struct {
Title string
@@ -23,14 +42,17 @@ type config struct {
}
// application holds the application-wide dependencies and configuration
-// This demonstrates a custom handler type that can hold state and dependencies
+// This serves as the central dependency injection container
type application struct {
- config config
- logger struct {
+ config config
+ logger struct {
info *log.Logger
error *log.Logger
}
serviceName string
+ // Future service dependencies (interfaces for clean injection)
+ // database Database
+ // mailer Mailer
}
// TemplateHandler represents handlers that render templates
@@ -139,17 +161,19 @@ func (app *application) processSubmit(w http.ResponseWriter, r *http.Request) {
}
// confirmWithToken handles magic link confirmation with token validation
-// This demonstrates a stateless handler function that can be converted with http.HandlerFunc
-func confirmWithToken(w http.ResponseWriter, r *http.Request) {
+// This demonstrates dependency injection by making it a method on application
+func (app *application) confirmWithToken(w http.ResponseWriter, r *http.Request) {
token := r.PathValue("token")
if !isValidToken(token) {
- w.Header().Add("Server", "buylater.email")
+ w.Header().Add("Server", app.serviceName)
+ app.logger.error.Printf("Invalid token attempted: %s", token)
http.NotFound(w, r)
return
}
- w.Header().Add("Server", "buylater.email")
+ w.Header().Add("Server", app.serviceName)
+ app.logger.info.Printf("Token confirmed successfully: %s", token[:8]+"...")
w.WriteHeader(http.StatusOK)
w.Write([]byte("Email Confirmed! Your purchase reminder has been activated via magic link."))
}
@@ -165,5 +189,77 @@ func isValidToken(token string) bool {
return matched
}
+// initializeApplication creates and configures the application with all dependencies
+// This centralizes dependency injection and makes testing easier
+func initializeApplication(cfg config) *application {
+ // Create structured loggers
+ infoLog := log.New(os.Stdout, "INFO\t", log.Ldate|log.Ltime)
+ errorLog := log.New(os.Stderr, "ERROR\t", log.Ldate|log.Ltime|log.Lshortfile)
+
+ // Log startup configuration
+ infoLog.Printf("Configuration loaded: port=%d, env=%s, staticDir=%s, htmlDir=%s",
+ cfg.port, cfg.env, cfg.staticDir, cfg.htmlDir)
+
+ // Create application with injected dependencies
+ app := &application{
+ config: cfg,
+ logger: struct {
+ info *log.Logger
+ error *log.Logger
+ }{
+ info: infoLog,
+ error: errorLog,
+ },
+ serviceName: "buylater.email",
+ // Future service dependencies will be initialized here:
+ // database: initializeDatabase(cfg),
+ // mailer: initializeMailer(cfg),
+ }
+
+ // Validate all required dependencies are properly initialized
+ if err := app.validateDependencies(); err != nil {
+ log.Fatalf("Dependency validation failed: %v", err)
+ }
+
+ app.logger.info.Println("Application dependencies initialized successfully")
+ return app
+}
+
+// validateDependencies ensures all required dependencies are properly configured
+func (app *application) validateDependencies() error {
+ // Validate configuration
+ if app.config.port <= 0 || app.config.port > 65535 {
+ return fmt.Errorf("invalid port: %d", app.config.port)
+ }
+ if app.config.env == "" {
+ return fmt.Errorf("environment not specified")
+ }
+ if app.config.staticDir == "" {
+ return fmt.Errorf("static directory not specified")
+ }
+ if app.config.htmlDir == "" {
+ return fmt.Errorf("HTML directory not specified")
+ }
+
+ // Validate loggers
+ if app.logger.info == nil {
+ return fmt.Errorf("info logger not initialized")
+ }
+ if app.logger.error == nil {
+ return fmt.Errorf("error logger not initialized")
+ }
+
+ // Validate service name
+ if app.serviceName == "" {
+ return fmt.Errorf("service name not specified")
+ }
+
+ // Future dependency validation will be added here:
+ // if app.database == nil { return fmt.Errorf("database not initialized") }
+ // if app.mailer == nil { return fmt.Errorf("mailer not initialized") }
+
+ return nil
+}
+
// about displays information about the buylater.email service
// This will be handled by the TemplateHandler type to demonstrate reusable custom handlers
cmd/web/main.go
@@ -35,27 +35,8 @@ func main() {
cfg.htmlDir = envHtmlDir
}
- // Create loggers for different types of messages
- infoLog := log.New(os.Stdout, "INFO\t", log.Ldate|log.Ltime)
- errorLog := log.New(os.Stderr, "ERROR\t", log.Ldate|log.Ltime|log.Lshortfile)
-
- // Log startup configuration
- infoLog.Printf("Configuration loaded: port=%d, env=%s, staticDir=%s, htmlDir=%s",
- cfg.port, cfg.env, cfg.staticDir, cfg.htmlDir)
-
- // Create an instance of the application struct, containing the application-wide
- // dependencies and configuration settings.
- app := &application{
- config: cfg,
- logger: struct {
- info *log.Logger
- error *log.Logger
- }{
- info: infoLog,
- error: errorLog,
- },
- serviceName: "buylater.email",
- }
+ // Initialize application dependencies
+ app := initializeApplication(cfg)
// Initialize a new servemux (router) - this stores the mapping between
// URL patterns and their corresponding handlers.
@@ -87,8 +68,8 @@ func main() {
// 3. Method handler with business logic
mux.Handle("POST /submit", http.HandlerFunc(app.processSubmit))
- // 4. Simple function handler converted to http.Handler
- mux.Handle("GET /confirm/{token}", http.HandlerFunc(confirmWithToken))
+ // 4. Method handler with dependency injection
+ mux.Handle("GET /confirm/{token}", http.HandlerFunc(app.confirmWithToken))
// 5. Another custom handler type instance for the about page
mux.Handle("GET /about", &TemplateHandler{
docs/project_plan.md
@@ -20,7 +20,7 @@ Phase 1 lays the groundwork by methodically working through the Let's Go book, c
| 1.10 | The HTTP Handler Interface | Completed | Medium | 2.10 | [task_1.10.md](todo/task_1.10.md) |
| 1.11 | Managing Configuration Settings | Completed | Medium | 3.1 | [task_1.11.md](todo/task_1.11.md) |
| 1.12 | Structured Logging | Completed | Medium | 3.2 | [task_1.12.md](todo/task_1.12.md) |
-| 1.13 | Dependency Injection | Pending | Medium | 3.3 | [task_1.13.md](todo/task_1.13.md) |
+| 1.13 | Dependency Injection | Completed | Medium | 3.3 | [task_1.13.md](todo/task_1.13.md) |
| 1.14 | Centralized Error Handling | Pending | Medium | 3.4 | [task_1.14.md](todo/task_1.14.md) |
| 1.15 | Isolating the Application Routes | Pending | Small | 3.5 | [task_1.15.md](todo/task_1.15.md) |