Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 33 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
name: CI

on:
push:
branches: [ main, master ]
pull_request:
branches: [ main, master ]

jobs:
golangci:
name: lint
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
with:
go-version: '1.23'
cache: false
- name: golangci-lint
uses: golangci/golangci-lint-action@v6
with:
version: v1.60

test:
name: test
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
with:
go-version: '1.23'
- name: Test
run: go test -v ./...
32 changes: 32 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
# Binaries for programs and plugins
*.exe
*.exe~
*.dll
*.so
*.dylib

# Test binary, built with `go test -c`
*.test

# Output of the go coverage tool, specifically when used with LiteIDE
*.out

# Dependency directories (remove the comment below to include it)
# vendor/

# Go workspace file
go.work

# IDE specific files
.idea/
.vscode/
*.swp
*.swo

# OS specific files
.DS_Store
Thumbs.db

# Project specific binaries
laravel-go
worker
107 changes: 106 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1 +1,106 @@
# laravel-go
# Laravel Go

A Go-based worker system compatible with Laravel's Queue and Schedule.

## Features

- **Queue Workers**: Consume jobs from Redis, Database, or SQS queues.
- **Job Handling**: Map Laravel job classes to Go handler functions.
- **PHP Serialization**: Support for `phpserialize` to read serialized PHP objects in job payloads.
- **Failed Jobs**: Automatically log failed jobs to a database table (compatible with Laravel's `failed_jobs`).
- **Scheduler**: A kernel scheduler similar to Laravel's, supporting `WithoutOverlapping` and `OnOneServer` using distributed locks.

## Installation

```bash
go get github.com/pixelvide/laravel-go
```

## Usage

### 1. Define Handlers

Create a handler function that matches the `queue.Handler` signature:

```go
func MyHandler(ctx context.Context, job *queue.Job) error {
log.Printf("Processing job: %s", job.Payload.DisplayName)
return nil
}
```

### 2. Register Handlers

Register the handler with the Laravel job class name:

```go
queue.Register("App\\Jobs\\ProcessPodcast", MyHandler)
```

### 3. Start Worker

```go
package main

import (
"context"
"github.com/pixelvide/laravel-go/pkg/config"
"github.com/pixelvide/laravel-go/pkg/driver/redis"
"github.com/pixelvide/laravel-go/pkg/worker"
)

func main() {
// Setup Redis Driver
driver := redis.NewRedisDriver(config.RedisConfig{
Addr: "localhost:6379",
})

// Setup Worker
w := worker.NewWorker(driver, nil, "default", 5)

// Run
w.Run(context.Background())
}
```

### SQS Driver

To use Amazon SQS:

```go
import "github.com/pixelvide/laravel-go/pkg/driver/sqs"

// ...

sqsClient, _ := config.LoadSQSClient(ctx, config.SQSConfig{
Region: "us-east-1",
QueueUrl: "https://sqs.us-east-1.amazonaws.com/123456789012/my-queue",
})
driver := sqs.NewSQSDriver(sqsClient, "https://sqs.us-east-1.amazonaws.com/123456789012/my-queue")
```

### Scheduler

The Scheduler allows running periodic tasks with distributed locking.

```go
import (
"github.com/pixelvide/laravel-go/pkg/schedule"
"github.com/pixelvide/laravel-go/pkg/driver/redis"
)

// ...

// Use Redis for distributed locks
redisClient := redis.NewRedisDriver(redisConfig).Client
lockProvider := schedule.NewRedisLockProvider(redisClient)

kernel := schedule.NewKernel(lockProvider)

// Register a task running every minute
kernel.Register("* * * * *", func() {
log.Println("Running task...")
}, schedule.OnOneServer("unique-task-name"))

kernel.Run()
```
90 changes: 90 additions & 0 deletions cmd/worker/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
package main

import (
"context"
"log"
"os"
"os/signal"
"syscall"

"github.com/pixelvide/laravel-go/pkg/config"
"github.com/pixelvide/laravel-go/pkg/driver/redis"
"github.com/pixelvide/laravel-go/pkg/queue"
"github.com/pixelvide/laravel-go/pkg/schedule"
"github.com/pixelvide/laravel-go/pkg/worker"
)

// ExampleHandler is a sample job handler
func ExampleHandler(ctx context.Context, job *queue.Job) error {
log.Printf("Processing job: %s, ID: %s", job.Payload.DisplayName, job.Payload.UUID)

// Example of accessing unserialized PHP data
if job.UnserializedData != nil {
// Map properties if needed
// props := queue.GetPHPProperty(job.UnserializedData, "podcastId")
log.Printf("Unserialized data present")
}
return nil
}

func main() {
// 1. Configure
redisConfig := config.RedisConfig{
Addr: "localhost:6379",
Password: "", // no password set
DB: 0, // use default DB
}

queueName := "default"
concurrency := 5

// 2. Register Handlers
// Register a handler for a hypothetical Laravel job "App\Jobs\ProcessPodcast"
queue.Register("App\\Jobs\\ProcessPodcast", ExampleHandler)

// 3. Initialize Driver
driver := redis.NewRedisDriver(redisConfig)

// 4. Initialize Worker
// For this example, we aren't setting up a database failed job provider, so we pass nil
w := worker.NewWorker(driver, nil, queueName, concurrency)

// 5. Run Worker with Graceful Shutdown
ctx, cancel := context.WithCancel(context.Background())
defer cancel()

// Handle SIGINT/SIGTERM
c := make(chan os.Signal, 1)
signal.Notify(c, os.Interrupt, syscall.SIGTERM)
go func() {
<-c
log.Println("Shutting down worker...")
cancel()
}()

log.Println("Starting worker pool...")

// Example: Run Scheduler (Optional)
// go runScheduler(redisConfig)

w.Run(ctx)
log.Println("Worker pool stopped.")
}

// runScheduler is an example function for setting up the scheduler.
// It is unused in the default worker configuration but provided as a reference.
//
//nolint:unused
func runScheduler(redisCfg config.RedisConfig) {
// Example of starting the scheduler
redisClient := redis.NewRedisDriver(redisCfg).Client

lockProvider := schedule.NewRedisLockProvider(redisClient)
kernel := schedule.NewKernel(lockProvider)

kernel.Register("* * * * *", func() {
log.Println("Running scheduled task...")
}, schedule.OnOneServer("my-scheduled-task"))

kernel.Run()
}
31 changes: 31 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
module github.com/pixelvide/laravel-go

go 1.23

require (
github.com/DATA-DOG/go-sqlmock v1.5.2
github.com/elliotchance/phpserialize v1.4.0
github.com/redis/go-redis/v9 v9.17.2
)

require (
github.com/aws/aws-sdk-go-v2 v1.41.0 // indirect
github.com/aws/aws-sdk-go-v2/config v1.32.6 // indirect
github.com/aws/aws-sdk-go-v2/credentials v1.19.6 // indirect
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.16 // indirect
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.16 // indirect
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.16 // indirect
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.4 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.4 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.16 // indirect
github.com/aws/aws-sdk-go-v2/service/signin v1.0.4 // indirect
github.com/aws/aws-sdk-go-v2/service/sqs v1.42.20 // indirect
github.com/aws/aws-sdk-go-v2/service/sso v1.30.8 // indirect
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.12 // indirect
github.com/aws/aws-sdk-go-v2/service/sts v1.41.5 // indirect
github.com/aws/smithy-go v1.24.0 // indirect
github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
github.com/robfig/cron/v3 v3.0.1 // indirect
github.com/yvasiyarov/php_session_decoder v0.0.0-20180803065642-a065a3b0b7d1 // indirect
)
49 changes: 49 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
github.com/DATA-DOG/go-sqlmock v1.5.2 h1:OcvFkGmslmlZibjAjaHm3L//6LiuBgolP7OputlJIzU=
github.com/DATA-DOG/go-sqlmock v1.5.2/go.mod h1:88MAG/4G7SMwSE3CeA0ZKzrT5CiOU3OJ+JlNzwDqpNU=
github.com/aws/aws-sdk-go-v2 v1.41.0 h1:tNvqh1s+v0vFYdA1xq0aOJH+Y5cRyZ5upu6roPgPKd4=
github.com/aws/aws-sdk-go-v2 v1.41.0/go.mod h1:MayyLB8y+buD9hZqkCW3kX1AKq07Y5pXxtgB+rRFhz0=
github.com/aws/aws-sdk-go-v2/config v1.32.6 h1:hFLBGUKjmLAekvi1evLi5hVvFQtSo3GYwi+Bx4lpJf8=
github.com/aws/aws-sdk-go-v2/config v1.32.6/go.mod h1:lcUL/gcd8WyjCrMnxez5OXkO3/rwcNmvfno62tnXNcI=
github.com/aws/aws-sdk-go-v2/credentials v1.19.6 h1:F9vWao2TwjV2MyiyVS+duza0NIRtAslgLUM0vTA1ZaE=
github.com/aws/aws-sdk-go-v2/credentials v1.19.6/go.mod h1:SgHzKjEVsdQr6Opor0ihgWtkWdfRAIwxYzSJ8O85VHY=
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.16 h1:80+uETIWS1BqjnN9uJ0dBUaETh+P1XwFy5vwHwK5r9k=
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.16/go.mod h1:wOOsYuxYuB/7FlnVtzeBYRcjSRtQpAW0hCP7tIULMwo=
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.16 h1:rgGwPzb82iBYSvHMHXc8h9mRoOUBZIGFgKb9qniaZZc=
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.16/go.mod h1:L/UxsGeKpGoIj6DxfhOWHWQ/kGKcd4I1VncE4++IyKA=
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.16 h1:1jtGzuV7c82xnqOVfx2F0xmJcOw5374L7N6juGW6x6U=
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.16/go.mod h1:M2E5OQf+XLe+SZGmmpaI2yy+J326aFf6/+54PoxSANc=
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.4 h1:WKuaxf++XKWlHWu9ECbMlha8WOEGm0OUEZqm4K/Gcfk=
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.4/go.mod h1:ZWy7j6v1vWGmPReu0iSGvRiise4YI5SkR3OHKTZ6Wuc=
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.4 h1:0ryTNEdJbzUCEWkVXEXoqlXV72J5keC1GvILMOuD00E=
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.4/go.mod h1:HQ4qwNZh32C3CBeO6iJLQlgtMzqeG17ziAA/3KDJFow=
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.16 h1:oHjJHeUy0ImIV0bsrX0X91GkV5nJAyv1l1CC9lnO0TI=
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.16/go.mod h1:iRSNGgOYmiYwSCXxXaKb9HfOEj40+oTKn8pTxMlYkRM=
github.com/aws/aws-sdk-go-v2/service/signin v1.0.4 h1:HpI7aMmJ+mm1wkSHIA2t5EaFFv5EFYXePW30p1EIrbQ=
github.com/aws/aws-sdk-go-v2/service/signin v1.0.4/go.mod h1:C5RdGMYGlfM0gYq/tifqgn4EbyX99V15P2V3R+VHbQU=
github.com/aws/aws-sdk-go-v2/service/sqs v1.42.20 h1:qa+1W+Kon3WDwO+8ugco4D9KvO0Pf0KBTn1hN7opIFw=
github.com/aws/aws-sdk-go-v2/service/sqs v1.42.20/go.mod h1:OG0Y3TgC+IeM++ngh+IcEkN24ruGsmRiAP8GUsOhMW8=
github.com/aws/aws-sdk-go-v2/service/sso v1.30.8 h1:aM/Q24rIlS3bRAhTyFurowU8A0SMyGDtEOY/l/s/1Uw=
github.com/aws/aws-sdk-go-v2/service/sso v1.30.8/go.mod h1:+fWt2UHSb4kS7Pu8y+BMBvJF0EWx+4H0hzNwtDNRTrg=
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.12 h1:AHDr0DaHIAo8c9t1emrzAlVDFp+iMMKnPdYy6XO4MCE=
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.12/go.mod h1:GQ73XawFFiWxyWXMHWfhiomvP3tXtdNar/fi8z18sx0=
github.com/aws/aws-sdk-go-v2/service/sts v1.41.5 h1:SciGFVNZ4mHdm7gpD1dgZYnCuVdX1s+lFTg4+4DOy70=
github.com/aws/aws-sdk-go-v2/service/sts v1.41.5/go.mod h1:iW40X4QBmUxdP+fZNOpfmkdMZqsovezbAeO+Ubiv2pk=
github.com/aws/smithy-go v1.24.0 h1:LpilSUItNPFr1eY85RYgTIg5eIEPtvFbskaFcmmIUnk=
github.com/aws/smithy-go v1.24.0/go.mod h1:LEj2LM3rBRQJxPZTB4KuzZkaZYnZPnvgIhb4pu07mx0=
github.com/bsm/ginkgo/v2 v2.12.0 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs=
github.com/bsm/ginkgo/v2 v2.12.0/go.mod h1:SwYbGRRDovPVboqFv0tPTcG1sN61LM1Z4ARdbAV9g4c=
github.com/bsm/gomega v1.27.10 h1:yeMWxP2pV2fG3FgAODIY8EiRE3dy0aeFYt4l7wh6yKA=
github.com/bsm/gomega v1.27.10/go.mod h1:JyEr/xRbxbtgWNi8tIEVPUYZ5Dzef52k01W3YH0H+O0=
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78=
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
github.com/elliotchance/phpserialize v1.4.0 h1:cAp/9+KSnEbUC8oYCE32n2n84BeW8HOY3HMDI8hG2OY=
github.com/elliotchance/phpserialize v1.4.0/go.mod h1:gt7XX9+ETUcLXbtTKEuyrqW3lcLUAeS/AnGZ2e49TZs=
github.com/kisielk/sqlstruct v0.0.0-20201105191214-5f3e10d3ab46/go.mod h1:yyMNCyc/Ib3bDTKd379tNMpB/7/H5TjM2Y9QJ5THLbE=
github.com/redis/go-redis/v9 v9.17.2 h1:P2EGsA4qVIM3Pp+aPocCJ7DguDHhqrXNhVcEp4ViluI=
github.com/redis/go-redis/v9 v9.17.2/go.mod h1:u410H11HMLoB+TP67dz8rL9s6QW2j76l0//kSOd3370=
github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs=
github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro=
github.com/yvasiyarov/php_session_decoder v0.0.0-20180803065642-a065a3b0b7d1 h1:p/oCPaHILUSplKqfjFyvivh4UglLHDtzs6F/wfOzyJE=
github.com/yvasiyarov/php_session_decoder v0.0.0-20180803065642-a065a3b0b7d1/go.mod h1:96w6piyt5Z2E86/J6EQPEn76UR4scqR9bS+Y9iJF/Og=
23 changes: 23 additions & 0 deletions pkg/config/config.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package config

// RedisConfig holds configuration for Redis connection
type RedisConfig struct {
Addr string
Password string
DB int
}

// DatabaseConfig holds configuration for SQL database connection
type DatabaseConfig struct {
Driver string // mysql, postgres
DSN string // Data Source Name
Table string // jobs table name, default "jobs"
}

// WorkerConfig holds configuration for the worker pool
type WorkerConfig struct {
QueueName string
Concurrency int
Redis *RedisConfig
Database *DatabaseConfig
}
Loading