204 lines
12 KiB
Markdown
204 lines
12 KiB
Markdown
# SummerCMS Technology Stack
|
|
|
|
## Philosophy
|
|
|
|
**Direct-style Scala 3 on JDK 21 virtual threads.** No monadic effect systems (no ZIO, no Cats Effect). Virtual threads make blocking IO cheap, so you write normal imperative-looking code that scales. This is the simplest possible modern Scala stack.
|
|
|
|
---
|
|
|
|
## Core Stack
|
|
|
|
| Layer | Technology | Why |
|
|
|-------|-----------|-----|
|
|
| **Language** | Scala 3.3+ LTS | VirtusLab maintains the compiler. Scala 3 has cleaner syntax, given/using for DI, extension methods, enums, union types |
|
|
| **Runtime** | JDK 21+ with Project Loom | Virtual threads = millions of lightweight threads, no async/callback hell |
|
|
| **HTTP Server** | Tapir + Netty | Tapir gives type-safe endpoint definitions with auto-generated OpenAPI docs. Netty-based server for production use |
|
|
| **Concurrency** | Ox | Structured concurrency, Go-like channels, error supervision, retries, rate limiting. Built on virtual threads |
|
|
| **Database** | Magnum | Scala 3 native, typesafe SQL interpolator, auto-generated CRUD, no dependencies, works with any JDBC database |
|
|
| **Migrations** | Liquibase | Supports rollbacks in free version (Flyway does not). Rollbacks are essential for dev workflow — drop last migration and fix, like in WinterCMS |
|
|
|
|
## Supporting Libraries
|
|
|
|
| Concern | Library | Notes |
|
|
|---------|---------|-------|
|
|
| **JSON** | jsoniter-scala | Fastest JSON lib on JVM. May need a custom wrapper (~75% done) |
|
|
| **Config** | [Jig](https://github.com/lbialy/jig) (HOCON) | Supports reading & writing including comments. Better discoverability for LLMs. Maintained in-house — we can add features as needed |
|
|
| **Logging** | Scribe + Scribe SLF4J binding | Excellent performance, configured in code (not XML). SLF4J binding for Java library compatibility |
|
|
| **Caching** | Caffeine (in-memory) + Jedis/Lettuce (Redis) | Caffeine is the fastest JVM cache; Redis for distributed |
|
|
| **Hashing** | jBCrypt + JDK crypto | Password hashing + general encryption. Bouncy Castle via JNI if performance-critical |
|
|
| **Email** | `summer-postcard` with driver trait | Clean interface + multiple drivers: SMTP, AWS SES, etc. |
|
|
| **HTTP Client** | sttp | Best HTTP client on JVM. Pairs with Tapir (same ecosystem). Handles sessions, cookies, streaming, HTTP auth |
|
|
| **Admin Frontend** | Vue.js 3 + TypeScript | SPA admin panel consuming Tapir API |
|
|
| **OpenAPI codegen** | Tapir -> OpenAPI -> openapi-typescript | Auto-generated TS types from Tapir endpoint definitions. Enables 1:1 API bindings + drift detection |
|
|
| **Template Engine** | Pebble | Twig-like syntax (preserves WinterCMS familiarity). Needs investigation: file-based loading + hot-reload in dev |
|
|
| **CLI** | case-app | Auto-generated help, parsed case classes, simple and LLM-friendly |
|
|
| **Testing** | MUnit + testcontainers-scala + Tapir test utils | MUnit for unit tests, testcontainers-scala for integration tests (Postgres etc.) |
|
|
| **Build (project)** | sbt | Multi-module build for the subprojects |
|
|
| **Build (scripts)** | scala-cli | Quick scripts, prototyping, dev tooling. VirtusLab maintains it |
|
|
|
|
---
|
|
|
|
## Module Mapping: Laravel Illuminate -> SummerCMS
|
|
|
|
Each module = separate sbt subproject, publishable independently (like Illuminate packages).
|
|
|
|
| Illuminate Package | SummerCMS Module | Implementation Approach |
|
|
|-------------------|-----------------|------------------------|
|
|
| **Container** | `summer-backpack` | Scala 3 `given`/`using` for compile-time DI + lightweight runtime registry for plugins |
|
|
| **Contracts** | `summer-pact` | Scala traits — all public APIs defined here |
|
|
| **Support** | `summer-towel` | Extension methods, utility types, base classes |
|
|
| **Config** | `summer-compass` | Jig-based HOCON config, supports reading & writing with comments, standardized access across all modules |
|
|
| **Events** | `summer-festival` | Simple event bus with typed events + Ox channels for async |
|
|
| **Http** | `summer-surf` | Tapir endpoint definitions + sttp client, request/response wrappers, routing (Tapir endpoints are type-safe routes) |
|
|
| **Database** | `summer-lagoon` | Magnum repos + immutable case class models + query builder + pagination. **Key challenge:** Scala immutability vs Eloquent/Doctrine mutation patterns — use `copy()` + repo.save() for single entities. Relations are the hardest part to model (see open questions) |
|
|
| **Auth** | `summer-bouncer` | JWT tokens (for API) + session cookies (for admin), guards as traits, session management. JWT via proven Java JWT library |
|
|
| **Validation** | `summer-lifeguard` | Compile-time via Scala types + runtime rules engine (inspired by fields.yaml) |
|
|
| **Cache** | `summer-cooler` | Caffeine + Redis, driver-based via trait |
|
|
| **Queue** | `summer-conga` | Persistent queue for offloading heavy work from the HTTP hot path. Driver-based: Postgres table, ActiveMQ, Redis, etc. Guarantees that enqueued jobs are not lost and are eventually executed. Virtual threads for workers |
|
|
| **Mail** | `summer-postcard` | Clean email interface with driver trait + template support. Drivers: SMTP, AWS SES, etc. |
|
|
| **Console** | `summer-bonfire` | CLI command framework via case-app for scaffolding (`summer create:plugin`, etc.) |
|
|
| **Filesystem** | `summer-sandcastle` | `java.nio.file` + pluggable storage providers (S3, etc.) via driver trait |
|
|
| **Translation** | `summer-phrasebook` | i18n with HOCON/JSON locale files |
|
|
| **View** | `summer-sunset` | Template engine for admin panel rendering |
|
|
| **Plugins** | `summer-party` | Plugin system: versioned plugin interfaces, resolution system, ServiceLoader-based runtime discovery. Private plugin registry with version compatibility extracted from published artifacts |
|
|
|
|
### Illuminate Packages Not Ported (and why)
|
|
|
|
| Illuminate Package | Why not needed | Where it lives instead |
|
|
|-------------------|----------------|----------------------|
|
|
| **Pipeline** | Function composition is native to Scala (`f andThen g`). Laravel needs a Pipeline class because PHP lacks first-class functions. In Scala this is a one-liner, not a module. | Inline wherever needed |
|
|
| **Routing** | Tapir endpoint definitions ARE routes — defining an endpoint and defining a route is the same thing. No separate routing layer needed. | Merged into `summer-surf` (http) |
|
|
| **Session** | For an API-first CMS with JWT, sessions are thin — only needed for admin panel cookies. Not enough to justify a standalone module. | Merged into `summer-bouncer` (auth) |
|
|
| **Log** | Scribe handles logging directly with code-based configuration. SLF4J binding provided for Java library compatibility. | Direct Scribe usage |
|
|
| **Collections** | Scala stdlib collections are already excellent — `List`, `Map`, `Seq`, `Vector` with `map`, `filter`, `fold`, etc. No wrapper needed. | Scala stdlib |
|
|
| **Pagination** | Two aspects: DB-level query pagination (in `summer-lagoon`) and template/API pagination helpers (in `summer-surf` / `summer-sunset`). Not enough for a standalone module. | Split across `summer-lagoon` + `summer-surf` |
|
|
|
|
---
|
|
|
|
## Plugin & Theme System
|
|
|
|
### Plugins (`summer-party`)
|
|
|
|
Plugins are published JVM artifacts (JARs), discovered at runtime via **ServiceLoader**.
|
|
|
|
- `summer-party` defines versioned plugin interfaces — a common contract all plugins implement
|
|
- Plugins discovered at boot via ServiceLoader (runtime-based, not classpath scanning)
|
|
- Version compatibility: registry extracts interface versions from published artifacts to know which plugins work with which CMS version
|
|
- Plugin descriptor declares: models, controllers, components, console commands, event listeners, navigation items
|
|
- Plugins can extend other plugins' models via Scala 3 extension methods + event hooks
|
|
- Plugin lifecycle: `register()` -> `boot()`
|
|
|
|
### Components
|
|
|
|
- Components are defined in plugins and placed in theme templates (like WinterCMS)
|
|
- Each component = a class with `onRun()`, properties, and a partial template
|
|
- Components handle their own data fetching and expose variables to templates
|
|
- Registered via plugin descriptor, auto-discovered by the theme engine
|
|
|
|
### Themes
|
|
|
|
- Theme = directory of templates + assets + config
|
|
- Templates rendered server-side (Pebble) or served as SPA shell
|
|
- Components (from plugins) can be placed in theme templates
|
|
|
|
---
|
|
|
|
## Admin Backend
|
|
|
|
- Tapir endpoints serve as the admin API
|
|
- **YAML/JSON-driven forms** — same concept as WinterCMS `fields.yaml` / `columns.yaml`:
|
|
- Plugin defines `fields.yaml` for model forms
|
|
- Backend returns form schema as JSON, Vue frontend renders it
|
|
- Field types: text, textarea, dropdown, relation, repeater, etc.
|
|
- **Admin frontend: Full SPA in Vue.js 3 + TypeScript** — consumes Tapir-generated typed API
|
|
- TypeScript types auto-generated from Tapir OpenAPI spec
|
|
- Vue components for form builder, list columns, relation managers, etc.
|
|
|
|
---
|
|
|
|
## Frontend-Backend Communication
|
|
|
|
- **Tapir-generated typed API** — all communication is via typed JSON endpoints
|
|
- OpenAPI spec auto-generated from Tapir endpoint definitions
|
|
- TypeScript client types generated from OpenAPI for the Vue admin SPA
|
|
- Public-facing sites can use the same API (headless mode) or server-rendered templates
|
|
- Headless mode: admin panel + API controllers serve as a superuser data presentation layer, frontend is fully decoupled
|
|
|
|
---
|
|
|
|
## Scaffolding Commands
|
|
|
|
```
|
|
summer create:plugin vendor.pluginname
|
|
summer create:model vendor.pluginname ModelName
|
|
summer create:controller vendor.pluginname ControllerName
|
|
summer create:component vendor.pluginname ComponentName
|
|
summer create:command vendor.pluginname CommandName
|
|
summer create:job vendor.pluginname JobName
|
|
summer create:migration vendor.pluginname description
|
|
```
|
|
|
|
---
|
|
|
|
## Project Structure
|
|
|
|
```
|
|
summercms/
|
|
build.sbt # Multi-project sbt build
|
|
modules/
|
|
summer-backpack/ # DI container + plugin registry
|
|
summer-pact/ # Contracts (traits)
|
|
summer-towel/ # Support utilities
|
|
summer-compass/ # Config
|
|
summer-festival/ # Events
|
|
summer-surf/ # HTTP + routing (Tapir)
|
|
summer-lagoon/ # Database + pagination (Magnum)
|
|
summer-bouncer/ # Auth + sessions
|
|
summer-lifeguard/ # Validation
|
|
summer-cooler/ # Cache
|
|
summer-conga/ # Queue (Ox)
|
|
summer-postcard/ # Mail
|
|
summer-bonfire/ # Console / CLI
|
|
summer-sandcastle/ # Filesystem + storage providers
|
|
summer-phrasebook/ # Translation / i18n
|
|
summer-sunset/ # View / templates
|
|
summer-party/ # Plugin system + registry
|
|
app/ # Full CMS application (depends on all modules)
|
|
summer-cms/ # Assembled CMS with admin panel
|
|
plugins/ # Example/core plugins
|
|
summer-plugin-user/
|
|
summer-plugin-blog/
|
|
summer-plugin-pages/
|
|
```
|
|
|
|
---
|
|
|
|
## Open Questions & Research Needed
|
|
|
|
### summer-lagoon ORM design (showstopper)
|
|
|
|
In PHP/Eloquent, models are mutable and carry their own persistence:
|
|
|
|
```php
|
|
$user = User::findByName("name");
|
|
$user->firstName = "Raziel";
|
|
$user->save();
|
|
```
|
|
|
|
In Scala, case classes are immutable. The equivalent pattern:
|
|
|
|
```scala
|
|
val user = UserRepo.findByName("name")
|
|
UserRepo.save(user.copy(firstName = "Raziel"))
|
|
```
|
|
|
|
Single-entity CRUD via `copy()` + repo is straightforward. **Relations are the hard part** — in Eloquent, relations are dynamic mutable fields on the model. Modeling this idiomatically in Scala with immutability needs deep design work. This is the biggest architectural risk in the project.
|
|
|
|
### Other open items
|
|
|
|
- **Pebble hot-reload**: Can Pebble load templates from filesystem (not classpath) and hot-reload on change? If not, implement file watcher that reloads the template engine on file changes. See [Pebble docs](https://deepwiki.com/PebbleTemplates/pebble)
|
|
- **JWT library**: Evaluate proven Java JWT libraries for token generation/validation
|
|
- **jsoniter-scala wrapper**: ~75% complete wrapper exists. Evaluate if it needs finishing or if raw jsoniter-scala is sufficient
|
|
- **API drift detection**: Static verification that services connect correctly post-deployment. Integrate into dev loop and CI
|
|
- **Plugin registry**: Design private plugin registry — extract interface versions from published artifacts to determine CMS version compatibility
|