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
44 changes: 22 additions & 22 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -6,30 +6,30 @@ VERSION = 0.1.1
help:
@echo "📋 Available targets:\n"
@echo "🏗️ Build & Setup:"
@echo " make start Build development image, boot stack, initialize db, and open browser"
@echo " make dev Build development Docker image"
@echo " make prod Build production Docker images (app + web)"
@echo " make up Boot the Docker stack"
@echo " make down Shut down the Docker stack"
@echo " make init Initialize app (composer, database, fixtures)"
@echo " make start - Build development image, boot stack, initialize db, and open browser"
@echo " make build - Build development Docker image"
@echo " make prod - Build production Docker images (app + web)"
@echo " make up - Boot the Docker stack"
@echo " make down - Shut down the Docker stack"
@echo " make init - Initialize app (composer, database, fixtures)"
@echo "\n🔄 Maintenance:"
@echo " make maintain Update composer and npm dependencies"
@echo " make show-composer-updates Show outdated composer packages"
@echo " make update-composer-dependencies Update composer packages"
@echo " make update-npm-dependencies Update npm packages"
@echo " make maintain - Update composer and npm dependencies"
@echo " make show-composer-updates - Show outdated composer packages"
@echo " make update-composer-dependencies - Update composer packages"
@echo " make update-npm-dependencies - Update npm packages"
@echo "\n🧪 Testing & Quality:"
@echo " make test Run backend and frontend tests"
@echo " make quality Run quality checks"
@echo " make phpstan Run static code analysis"
@echo " make style Fix code style"
@echo " make arch Test architecture"
@echo " make coverage Generate coverage report"
@echo "\n🛠️ Development:"
@echo " make shell Open shell on app container"
@echo " make composer Run composer command (use: make composer cmd='install')"
@echo " make npm-build Create frontend build"
@echo " make clear Clear all caches"
@echo " make open Open application in browser\n"
@echo " make test - Run backend and frontend tests"
@echo " make quality - Run quality checks"
@echo " make phpstan - Run static code analysis"
@echo " make style - Fix code style"
@echo " make arch - Test architecture"
@echo " make coverage - Generate coverage report"
@echo "\n🛠️ Development:"
@echo " make shell - Open shell on app container"
@echo " make composer - Run composer command (use: make composer cmd='install')"
@echo " make npm-build - Create frontend build"
@echo " make clear - Clear all caches"
@echo " make open - Open application in browser\n"

start: build up init open

Expand Down
111 changes: 60 additions & 51 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,13 @@
![app-ci-workflow](https://github.com/makomweb/split-fairly/actions/workflows/app-ci.yaml/badge.svg)
[![codecov](https://codecov.io/gh/makomweb/split-fairly/graph/badge.svg?token=O6WQ8USL6T)](https://codecov.io/gh/makomweb/split-fairly)

A modern web application for transparently splitting expenses and settling debts among groups. Built with **event sourcing** to maintain a complete audit trail of all financial transactions.
## Goal

Follows DDD principles for separating the application into generic, core, and supporting domains - enforced by [deptrac](https://github.com/deptrac/deptrac).
This is a full-stack, cloud-native web application for tracking and splitting expenses and settling debts among 2 individuals. It is built on PHP 8.4, Symfony 8, MySQL 8, and React 19, and uses **event sourcing** to maintain a complete audit trail of all financial transactions.

It follows DDD principles to separate the application into generic, core, and supporting domains — enforced by [deptrac](https://github.com/deptrac/deptrac). This makes it easy to achieve full code coverage for the core domain.

It also showcases Kubernetes deployment using Helm.

## Screenshots

Expand Down Expand Up @@ -88,66 +92,71 @@ Visit `http://localhost:8000` in your browser.
make prod

# Deploy to cluster
helm install app ./helm
# or:
helm upgrade --install app ./helm

# Watch pods come up
kubectl get pods -w

# View application logs
kubectl logs deployment/app-split-fairly-app -f
kubectl logs deployment/app-split-fairly-worker -f

# Access the application
# - Direct: http://localhost:30190
# - Port-forward: kubectl port-forward svc/app-split-fairly-web 8080:80
# View logs for all pods with the PHP label (app + worker)
kubectl logs -f -l technology=php

# Login credentials (auto-loaded from fixtures)
# Email: admin@example.com
# Password: secret
# Access the application via NodePort:
# Link: http://localhost:30190
# Or configure port forwarding via:
kubectl port-forward svc/app-split-fairly-web 8080:80
# Link: http://localhost:8080
```

### Kubernetes Architecture

```
┌─────────────────────────────────────────────────────────────┐
│ Kubernetes Cluster │
├─────────────────────────────────────────────────────────────┤
│ │
│ ┌──────────────────┐ ┌──────────────────┐ │
│ │ nginx (web) │ │ app (PHP-FPM) │ │
│ │ Split-Fairly │ │ Deployment │ │
│ │ NodePort:30190 │─→│ Pods × 1 │ │
│ └──────────────────┘ └──────────────────┘ │
│ │ │ │
│ │ Serves SPA │ Processes requests │
│ │ EasyAdmin │ Event sourcing │
│ │ │ Session management │
│ │ ┌─────▼──────┐ │
│ │ │ worker │ │
│ │ │ Pod × 1 │ │
│ │ │ Async jobs │ │
│ │ └────────────┘ │
│ │ │ │
│ └───────────┬───────────┘ │
│ │ │
│ ┌──────▼──────┐ │
│ │ MySQL │ │
│ │ StatefulSet │ │
│ │ PVC Storage │ │
│ └─────────────┘ │
│ △ │
│ │ init Job │
│ ┌──────┴──────┐ │
│ │ db-init │ │
│ │ (one-time) │ │
│ └─────────────┘ │
│ │
└─────────────────────────────────────────────────────────────┘
┌────────────────────────────────────────────────────────────────────────┐
│ Kubernetes Cluster │
├────────────────────────────────────────────────────────────────────────┤
│ │
│ ┌──────────────────┐ ┌──────────────────┐ ┌──────────────────┐ │
│ │ nginx (web) │ │ app (PHP-FPM) │ │ worker │ │
│ │ Deployment │ │ Deployment │ │ (Messenger) │ │
│ │ NodePort:30190 │ │ Pod × 1 │ │ Pod × 1 │ │
│ │ Pods × 1 │─→│ FastCGI :9000 │ │ One-shot pattern │ │
│ └──────────────────┘ └──────────────────┘ └──────────────────┘ │
│ │ │ │ │
│ │ Serves SPA │ Business logic │ Async tasks │
│ │ EasyAdmin │ API endpoints │ from queue │
│ │ Static assets │ Session mgmt (DB) │ │
│ │ │ Event sourcing (DB) │ │
│ │ │ │ │
│ └───────────┬───────────┴───────────┬───────────┘ │
│ │ │ │
│ ┌──────▼───────────────────────▼──┐ │
│ │ MySQL StatefulSet │ │
│ │ PVC Storage (8Gi) │ │
│ │ - Event store │ │
│ │ - Sessions │ │
│ │ - Application data │ │
│ └─────────────────────────────────┘ │
│ △ │
│ │ init Job │
│ ┌──────┴──────┐ │
│ │ db-init │ │
│ │ (one-time) │ │
│ └─────────────┘ │
│ │
│ ┌──────────────────────────────────────────────────────────────┐ │
│ │ Grafana Alloy (k8s-monitoring) │ │
│ │ - Collects logs from all pods │ │
│ │ - Metrics collection & forwarding │ │
│ └──────────────────────────────────────────────────────────────┘ │
│ │
└────────────────────────────────────────────────────────────────────────┘
```

**Components:**
- **nginx (web)**: Serves React SPA frontend, EasyAdmin assets, proxies API to PHP
- **PHP-FPM (app)**: Symfony backend, handles business logic & API endpoints
- **Worker**: Processes async jobs via Messenger (background tasks)
- **MySQL**: Persistent data storage with PVC
- **db-init Job**: One-time database initialization (schema + fixtures)
- **nginx (web)**: Serves React SPA frontend, static assets, EasyAdmin UI. Proxies API requests to PHP-FPM via FastCGI.
- **PHP-FPM (app)**: Symfony backend handling business logic, API endpoints, and session management. Stores sessions in MySQL.
- **Worker**: Processes async jobs via Messenger with one-shot pattern (processes single message per pod lifecycle, then restarts for fresh environment).
- **MySQL**: Persistent data storage with StatefulSet and PVC. Stores event sourcing audit trail, sessions, and application data.
- **db-init Job**: One-time database initialization (schema, fixtures, migrations).
- **Grafana Alloy**: Log collection and forwarding for observability across all cluster components.
19 changes: 10 additions & 9 deletions backend/composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,13 @@
"ext-ctype": "*",
"ext-iconv": "*",
"doctrine/doctrine-bundle": "^3.2.2",
"doctrine/doctrine-fixtures-bundle": "^4.3",
"doctrine/doctrine-fixtures-bundle": "^4.3.1",
"doctrine/doctrine-migrations-bundle": "^4.0",
"doctrine/orm": "^3.6.2",
"dompdf/dompdf": "^3.1",
"easycorp/easyadmin-bundle": "^4.28",
"dompdf/dompdf": "^3.1.5",
"easycorp/easyadmin-bundle": "^5.0.1",
"nelmio/cors-bundle": "^2.6.1",
"phpdocumentor/reflection-docblock": "^5.6.6",
"phpdocumentor/reflection-docblock": "^6.0.2",
"phpstan/phpdoc-parser": "^2.3.2",
"symfony/browser-kit": "8.0.*",
"symfony/console": "8.0.*",
Expand All @@ -25,6 +25,7 @@
"symfony/http-kernel": "8.0.*",
"symfony/intl": "8.0.*",
"symfony/messenger": "8.0.*",
"symfony/monolog-bundle": "^4.0.1",
"symfony/property-access": "8.0.*",
"symfony/property-info": "8.0.*",
"symfony/runtime": "8.0.*",
Expand Down Expand Up @@ -104,11 +105,11 @@
}
},
"require-dev": {
"deptrac/deptrac": "^4.5",
"friendsofphp/php-cs-fixer": "^3.93.1",
"phpstan/phpstan": "^2.1.38",
"phpunit/phpunit": "^12.5.8",
"symfony/maker-bundle": "^1.65.1",
"deptrac/deptrac": "^4.6",
"friendsofphp/php-cs-fixer": "^3.94.2",
"phpstan/phpstan": "^2.1.40",
"phpunit/phpunit": "^13.0.5",
"symfony/maker-bundle": "^1.66.0",
"symfony/stopwatch": "8.0.*",
"symfony/web-profiler-bundle": "8.0.*"
}
Expand Down
Loading