Files
2026-02-04 02:18:31 +01:00

795 lines
27 KiB
Markdown

# Architecture Research: SummerCMS
**Domain:** Scala/ZIO-based Content Management Framework (CMF)
**Researched:** 2026-02-04
**Overall Confidence:** MEDIUM-HIGH
This document defines the recommended architecture for SummerCMS, a Scala rewrite of WinterCMS using the ZIO ecosystem. The architecture leverages ZIO's effect system and layer-based dependency injection to create a modular, extensible CMF.
---
## High-Level Architecture
```
+------------------+
| HTMX/Vue |
| Frontend |
+--------+---------+
|
v
+-----------------------------------------------------------------------------------+
| HTTP LAYER (ZIO HTTP) |
| +-------------+ +-------------+ +-------------+ +------------------+ |
| | Public | | Admin | | API | | Static Assets | |
| | Routes | | Routes | | Routes | | Handler | |
| +------+------+ +------+------+ +------+------+ +------------------+ |
| | | | |
| +----------------+----------------+ |
| | |
| v |
| +--------------------------------------------------------------------+ |
| | MIDDLEWARE STACK | |
| | Auth | CORS | Logging | Metrics | Error | Rate Limit | Plugin | |
| +--------------------------------------------------------------------+ |
+-----------------------------------------------------------------------------------+
|
v
+-----------------------------------------------------------------------------------+
| REQUEST PIPELINE |
| +-------------+ +-------------+ +-------------+ +------------------+ |
| | Route | | Theme | | Component | | Response | |
| | Resolver | | Resolver | | Resolver | | Builder | |
| +------+------+ +------+------+ +------+------+ +------------------+ |
+-----------------------------------------------------------------------------------+
|
v
+-----------------------------------------------------------------------------------+
| CORE SERVICES LAYER |
| +-----------+ +-----------+ +-----------+ +-----------+ +-----------+ |
| | Plugin | | Theme | | Config | | Event | | Cache | |
| | Registry | | Engine | | Service | | Bus | | Service | |
| +-----------+ +-----------+ +-----------+ +-----------+ +-----------+ |
| |
| +-----------+ +-----------+ +-----------+ +-----------+ +-----------+ |
| | Template | | Form | | Auth | | Media | | Queue | |
| | Engine | | Builder | | Service | | Manager | | Service | |
| +-----------+ +-----------+ +-----------+ +-----------+ +-----------+ |
+-----------------------------------------------------------------------------------+
|
v
+-----------------------------------------------------------------------------------+
| DOMAIN LAYER (Pure Business Logic) |
| +-----------+ +-----------+ +-----------+ +-----------+ +-----------+ |
| | Models | | Commands | | Queries | | Validators| | Transforms| |
| +-----------+ +-----------+ +-----------+ +-----------+ +-----------+ |
+-----------------------------------------------------------------------------------+
|
v
+-----------------------------------------------------------------------------------+
| DATA ACCESS LAYER |
| +-----------+ +-----------+ +-----------+ +-----------+ |
| | Repository| | Query | | Migration | | Schema | |
| | Interfaces| | Builder | | System | | Registry | |
| +-----------+ +-----------+ +-----------+ +-----------+ |
| | |
| v |
| +--------------------------------------------------------------------+ |
| | ZIO Quill (PostgreSQL) | |
| +--------------------------------------------------------------------+ |
+-----------------------------------------------------------------------------------+
```
---
## Core Components
### 1. Request Pipeline
**Responsibility:** Routes incoming HTTP requests through theme/component resolution to response generation.
```scala
// Conceptual flow
Request
-> RouteResolver // Determines page/route from URL
-> ThemeResolver // Loads theme and layout
-> ComponentResolver // Instantiates page components
-> PageRenderer // Renders template with component data
-> Response
```
**Key Types:**
```scala
trait RouteResolver {
def resolve(path: Path, method: Method): ZIO[Any, RouteError, ResolvedRoute]
}
sealed trait ResolvedRoute
case class PageRoute(page: Page, params: Map[String, String]) extends ResolvedRoute
case class ApiRoute(handler: Handler, params: Map[String, String]) extends ResolvedRoute
case class StaticRoute(asset: Asset) extends ResolvedRoute
case class RedirectRoute(target: String, permanent: Boolean) extends ResolvedRoute
```
**Build Order:** 2nd (after Plugin System foundation)
---
### 2. Plugin System
**Responsibility:** Plugin discovery, lifecycle management, dependency resolution, and extension point registry.
This is the foundational component. Everything else builds on top of it.
```scala
// Plugin definition (similar to WinterCMS Plugin.php)
trait Plugin {
def pluginDetails: PluginDetails
def require: List[PluginId] = Nil // Dependencies
// Lifecycle hooks
def register: ZIO[PluginEnvironment, Nothing, Unit]
def boot: ZIO[PluginEnvironment, PluginError, Unit]
// Registration methods
def registerComponents: Map[String, ComponentFactory] = Map.empty
def registerNavigation: List[NavigationItem] = List.empty
def registerSettings: List[SettingsPage] = List.empty
def registerPermissions: List[Permission] = List.empty
def registerMailTemplates: List[MailTemplate] = List.empty
def registerSchedule: List[ScheduledTask] = List.empty
}
// Plugin registry service
trait PluginRegistry {
def discover: ZIO[Any, PluginError, List[PluginManifest]]
def load(id: PluginId): ZIO[Any, PluginError, Plugin]
def enable(id: PluginId): ZIO[Any, PluginError, Unit]
def disable(id: PluginId): ZIO[Any, PluginError, Unit]
def getEnabled: ZIO[Any, Nothing, List[Plugin]]
def resolveDependencies(id: PluginId): ZIO[Any, PluginError, List[PluginId]]
}
```
**Plugin Extension Mechanism (replicating WinterCMS patterns):**
```scala
// 1. Event-based extension (primary mechanism)
trait EventBus {
def emit[E](event: E): ZIO[Any, Nothing, Unit]
def subscribe[E: Tag](handler: E => ZIO[Any, Nothing, Unit]): ZIO[Any, Nothing, Subscription]
}
// Events plugins can emit/subscribe to
case class ModelCreated[M](model: M)
case class ModelUpdated[M](model: M, changes: Map[String, Any])
case class FormExtending(formWidget: FormWidget, model: Any, context: String)
case class ListExtending(listWidget: ListWidget, model: Any)
// 2. Model extension via type classes
trait ModelExtensions[M] {
def relations: Map[String, Relation[_, _]] = Map.empty
def attributes: Map[String, Attribute[_]] = Map.empty
def behaviors: List[Behavior[M]] = List.empty
}
// 3. Form field injection
trait FormFieldExtension {
def extendFields(form: FormDefinition, model: Any, context: String): FormDefinition
}
```
**ZIO Layer Structure:**
```scala
val pluginRegistryLayer: ZLayer[Config & Database, PluginError, PluginRegistry] =
ZLayer.fromFunction(PluginRegistryLive.apply _)
val eventBusLayer: ZLayer[Any, Nothing, EventBus] =
ZLayer.succeed(EventBusLive())
```
**Build Order:** 1st (foundation for everything)
---
### 3. Theme Engine
**Responsibility:** Theme loading, template rendering, asset management, layout composition.
```scala
trait ThemeEngine {
def getActive: ZIO[Any, ThemeError, Theme]
def setActive(id: ThemeId): ZIO[Any, ThemeError, Unit]
def render(page: Page, data: PageData): ZIO[Any, RenderError, Html]
def renderPartial(name: String, data: Map[String, Any]): ZIO[Any, RenderError, Html]
}
trait Theme {
def id: ThemeId
def pages: ZIO[Any, ThemeError, List[PageDefinition]]
def layouts: ZIO[Any, ThemeError, List[LayoutDefinition]]
def partials: ZIO[Any, ThemeError, List[PartialDefinition]]
def assets: AssetManager
}
// Template definition (parsed from .htm files with YAML frontmatter)
case class PageDefinition(
url: String,
layout: Option[String],
title: Option[String],
description: Option[String],
components: Map[String, ComponentConfig], // Component alias -> config
markup: String
)
```
**Template Rendering Options:**
| Option | Recommendation | Rationale |
|--------|---------------|-----------|
| ScalaTags | **Recommended** | Type-safe, fast (2x Twirl), Scala-native, HTMX-friendly |
| Twirl | Alternative | Play Framework standard, familiar syntax |
| Custom Parser | For WinterCMS .htm compat | Parse YAML frontmatter + Twig-like syntax |
For maximum WinterCMS compatibility, implement a custom template parser that:
1. Parses YAML frontmatter for page config
2. Translates Twig-like syntax to ScalaTags calls
3. Supports component embedding via `{% component 'alias' %}`
**Build Order:** 4th (needs Plugin System, Config, Components)
---
### 4. Component System
**Responsibility:** Reusable UI components with lifecycle, properties, and AJAX handlers.
```scala
// Component definition (mirrors WinterCMS components)
trait Component {
def componentDetails: ComponentDetails
def defineProperties: List[PropertyDefinition] = Nil
// Lifecycle
def init: ZIO[ComponentEnv, ComponentError, Unit] = ZIO.unit
def onRun: ZIO[ComponentEnv, ComponentError, Unit] = ZIO.unit
// Data for template
def data: ZIO[ComponentEnv, ComponentError, Map[String, Any]]
// AJAX/HTMX handlers (replaces WinterCMS onXxx methods)
def handlers: Map[String, Handler] = Map.empty
}
// Handler for HTMX requests
type Handler = Request => ZIO[ComponentEnv, ComponentError, Response]
// Component factory for registration
trait ComponentFactory {
def create(config: ComponentConfig): ZIO[Any, ComponentError, Component]
}
// Property definition (for admin UI)
case class PropertyDefinition(
name: String,
title: String,
description: Option[String],
`type`: PropertyType, // string, dropdown, checkbox, etc.
default: Option[Any],
options: Option[Map[String, String]], // For dropdowns
required: Boolean = false,
validation: List[Validator] = Nil
)
```
**HTMX Integration:**
```scala
// Component can define HTMX handlers
trait Component {
// Traditional handler approach
def onLoadMore(request: Request): ZIO[ComponentEnv, ComponentError, Response] = ???
// These become routes: POST /component/{alias}/onLoadMore
// HTMX attributes: hx-post="/component/posts/onLoadMore" hx-target="#posts-list"
}
```
**Build Order:** 3rd (needs Plugin System, Config)
---
### 5. Admin Backend
**Responsibility:** Backend administration interface with YAML-driven forms and lists.
```scala
// Backend controller (mirrors WinterCMS Backend\Classes\Controller)
trait BackendController {
def actions: Map[String, Action] = Map.empty
// Standard CRUD actions
def index: Action = listAction
def create: Action = formAction(FormMode.Create)
def update(id: Id): Action = formAction(FormMode.Update, Some(id))
def preview(id: Id): Action = formAction(FormMode.Preview, Some(id))
}
// Form configuration (parsed from fields.yaml)
case class FormConfig(
fields: Map[String, FieldDefinition],
tabs: Option[TabsConfig],
secondaryTabs: Option[TabsConfig]
)
case class FieldDefinition(
label: String,
`type`: FieldType, // text, textarea, dropdown, relation, etc.
span: Span = Span.Auto, // left, right, full, auto
required: Boolean = false,
placeholder: Option[String] = None,
options: Option[FieldOptions] = None,
dependsOn: List[String] = Nil,
trigger: Option[TriggerConfig] = None
)
// List configuration (parsed from columns.yaml)
case class ListConfig(
columns: Map[String, ColumnDefinition],
recordUrl: Option[String],
perPage: Int = 20,
showCheckboxes: Boolean = true,
toolbar: ToolbarConfig
)
```
**YAML Parsing:**
```scala
// Use zio-json-yaml or yaml4s for parsing
val formConfig: ZIO[Any, ParseError, FormConfig] =
YamlParser.parse[FormConfig](yamlContent)
```
**Build Order:** 6th (needs most other components)
---
### 6. Database Layer
**Responsibility:** Type-safe database access, migrations, model definitions.
**Recommendation: ZIO Quill** because:
- Compile-time query generation (catches SQL errors at compile time)
- Native ZIO integration via `quill-jdbc-zio`
- Excellent PostgreSQL support
- Minimal runtime overhead
```scala
// Model definition (case class + schema)
case class Post(
id: Long,
title: String,
slug: String,
content: Option[String],
publishedAt: Option[Instant],
authorId: Long,
createdAt: Instant,
updatedAt: Instant
)
object Post {
implicit val schema: Schema[Post] = DeriveSchema.gen[Post]
// Quill query definitions
inline def posts = quote { query[Post] }
inline def bySlug(slug: String) = quote { posts.filter(_.slug == lift(slug)) }
inline def published = quote { posts.filter(_.publishedAt.isDefined) }
}
// Repository pattern with ZIO
trait PostRepository {
def findById(id: Long): ZIO[Any, RepositoryError, Option[Post]]
def findBySlug(slug: String): ZIO[Any, RepositoryError, Option[Post]]
def findPublished(page: Int, perPage: Int): ZIO[Any, RepositoryError, Page[Post]]
def create(post: Post): ZIO[Any, RepositoryError, Post]
def update(post: Post): ZIO[Any, RepositoryError, Post]
def delete(id: Long): ZIO[Any, RepositoryError, Unit]
}
// Implementation using Quill
class PostRepositoryLive(quill: Quill.Postgres[SnakeCase]) extends PostRepository {
import quill._
def findById(id: Long): ZIO[Any, RepositoryError, Option[Post]] =
run(quote { Post.posts.filter(_.id == lift(id)) })
.map(_.headOption)
.mapError(RepositoryError.fromThrowable)
}
```
**Migration System:**
```scala
// Migrations as versioned ZIO effects
trait Migration {
def version: MigrationVersion
def description: String
def up: ZIO[Database, MigrationError, Unit]
def down: ZIO[Database, MigrationError, Unit]
}
// Plugin migrations
trait Plugin {
def migrations: List[Migration] = List.empty
}
```
**Build Order:** 1st-parallel (can be built alongside Plugin System foundation)
---
### 7. Authentication & Authorization
**Responsibility:** User authentication, JWT tokens, permissions, access control.
```scala
// Auth service
trait AuthService {
def authenticate(credentials: Credentials): ZIO[Any, AuthError, User]
def validateToken(token: JwtToken): ZIO[Any, AuthError, User]
def generateToken(user: User): ZIO[Any, AuthError, JwtToken]
def refreshToken(token: JwtToken): ZIO[Any, AuthError, JwtToken]
def hasPermission(user: User, permission: Permission): ZIO[Any, Nothing, Boolean]
}
// Middleware for protected routes
val authMiddleware: Middleware[AuthService] =
Middleware.interceptIncomingHandler { request =>
for {
token <- extractBearerToken(request)
user <- ZIO.serviceWithZIO[AuthService](_.validateToken(token))
} yield (request, Context(user))
}
// Permission-based access control
val requirePermission: Permission => Middleware[AuthService] = permission =>
Middleware.interceptIncomingHandler { (request, ctx: Context) =>
ZIO.serviceWithZIO[AuthService](_.hasPermission(ctx.user, permission))
.filterOrFail(identity)(AuthError.Forbidden)
.as((request, ctx))
}
```
**Build Order:** 5th (needs Database, Plugin System, Config)
---
### 8. Configuration Service
**Responsibility:** Hierarchical configuration from files, database, environment.
```scala
trait ConfigService {
def get[A: Schema](key: String): ZIO[Any, ConfigError, A]
def getOpt[A: Schema](key: String): ZIO[Any, ConfigError, Option[A]]
def set[A: Schema](key: String, value: A): ZIO[Any, ConfigError, Unit]
}
// Configuration sources (in priority order)
// 1. Environment variables
// 2. Database (system_settings table)
// 3. Plugin config files (config/*.yaml)
// 4. Theme config files
// 5. Defaults
```
**Build Order:** 1st-parallel (early foundation)
---
## Component Boundaries
| Component | Owns | Does NOT Own |
|-----------|------|--------------|
| **Plugin System** | Plugin lifecycle, dependencies, discovery, extension registry | Specific plugin logic |
| **Theme Engine** | Template loading, rendering, layouts, assets | Page data (components provide this) |
| **Component System** | Component lifecycle, properties, handlers | Database access (uses Repository) |
| **Admin Backend** | Form/list rendering, CRUD scaffolding | Business logic (delegates to services) |
| **Database Layer** | Connections, queries, migrations, schemas | Business rules (pure domain layer) |
| **Auth Service** | Authentication, tokens, permissions | User registration flows (User plugin) |
| **Config Service** | Config loading, caching, hierarchy | Config UI (Admin Backend) |
| **Event Bus** | Event dispatch, subscriptions | Event definitions (plugins define these) |
---
## Data Flow
### Frontend Page Request
```
1. Browser: GET /blog/my-post
|
2. ZIO HTTP: Route matching
|
3. RouteResolver: Lookup page by URL pattern
|-- Database: pages table (or theme file scan)
|-- Returns: PageDefinition { url: "/blog/:slug", components: ["blogPost"] }
|
4. ThemeResolver: Load theme, layout, page template
|-- Filesystem: themes/mytheme/pages/blog-post.htm
|-- Returns: LayoutDefinition, PageTemplate
|
5. ComponentResolver: Instantiate components
|-- PluginRegistry: Get component factories
|-- For each component in page:
| |-- Factory.create(config)
| |-- Component.init()
| |-- Component.onRun()
| |-- Component.data() -> Map[String, Any]
|
6. PageRenderer: Render template with component data
|-- Merge: layout + page + partial templates
|-- Inject: component data into template scope
|-- Execute: ScalaTags/template -> Html
|
7. Response: HTML with HTMX attributes
```
### HTMX Handler Request
```
1. Browser: POST /component/blogPosts/onLoadMore (HTMX)
|-- Headers: HX-Request: true, HX-Trigger: load-more-btn
|
2. ZIO HTTP: Component handler route
|
3. ComponentResolver:
|-- Load component by alias from session/page context
|-- Component.handlers("onLoadMore")
|
4. Handler execution:
|-- Handler(request) -> ZIO[ComponentEnv, Error, Response]
|-- Returns: HTML fragment
|
5. Response: HTML fragment
|-- HTMX swaps into target element
```
### Admin Form Save
```
1. Admin: POST /backend/blog/posts/update/5
|-- Body: form data (multipart or JSON)
|
2. BackendController.update(5):
|-- FormWidget.load(fields.yaml)
|-- Validate input against field definitions
|-- Event: FormSaving(widget, model, data) // Plugins can modify
|
3. Model save:
|-- Repository.update(model)
|-- Event: ModelUpdated(model, changes) // Plugins react
|
4. Response: Redirect to list or show success flash
```
---
## ZIO Patterns
### Layer Architecture
```scala
// Application layers (bottom-up)
val dataLayer: ZLayer[Any, Nothing, Database & Cache] =
ZLayer.make[Database & Cache](
PostgresDatabase.layer,
RedisCache.layer
)
val coreServicesLayer: ZLayer[Database & Cache, Nothing, Services] =
ZLayer.make[Services](
ConfigServiceLive.layer,
EventBusLive.layer,
AuthServiceLive.layer,
PluginRegistryLive.layer
)
val domainLayer: ZLayer[Services & Database, Nothing, Repositories] =
ZLayer.make[Repositories](
PostRepositoryLive.layer,
UserRepositoryLive.layer,
// ... more repositories
)
val httpLayer: ZLayer[Services & Repositories, Nothing, HttpApp] =
ZLayer.make[HttpApp](
PublicRoutes.layer,
AdminRoutes.layer,
ApiRoutes.layer,
MiddlewareStack.layer
)
// Full application
val appLayer: ZLayer[Any, AppError, HttpApp] =
dataLayer >>> coreServicesLayer >>> domainLayer >>> httpLayer
```
### Effect Organization
```scala
// Pure business logic (no effects)
object PostLogic {
def generateSlug(title: String): String =
title.toLowerCase.replaceAll("[^a-z0-9]+", "-")
def validatePost(post: Post): Validated[ValidationError, Post] = ???
}
// Effectful operations wrapped in ZIO
trait PostService {
def createPost(input: CreatePostInput): ZIO[Any, PostError, Post]
}
class PostServiceLive(
repo: PostRepository,
events: EventBus,
auth: AuthService
) extends PostService {
def createPost(input: CreatePostInput): ZIO[Any, PostError, Post] =
for {
_ <- auth.requirePermission(Permission.CreatePost)
slug <- ZIO.succeed(PostLogic.generateSlug(input.title))
valid <- ZIO.fromEither(PostLogic.validatePost(input.toPost(slug)))
created <- repo.create(valid)
_ <- events.emit(PostCreated(created))
} yield created
}
```
### Resource Safety
```scala
// Database transactions
def withTransaction[R, E, A](zio: ZIO[R & Transaction, E, A]): ZIO[R & Database, E, A] =
ZIO.scoped {
for {
tx <- Database.beginTransaction
result <- zio.provideSome[R](ZLayer.succeed(tx))
_ <- tx.commit
} yield result
}
// Plugin lifecycle with proper cleanup
def loadPlugins: ZIO[Scope & PluginRegistry, PluginError, List[Plugin]] =
for {
registry <- ZIO.service[PluginRegistry]
plugins <- registry.discover
sorted <- registry.topologicalSort(plugins) // Dependency order
loaded <- ZIO.foreach(sorted) { manifest =>
ZIO.acquireRelease(
registry.load(manifest.id).tap(_.boot)
)(plugin => plugin.shutdown.ignore)
}
} yield loaded
```
### Error Handling
```scala
// Domain errors as sealed traits
sealed trait PostError
object PostError {
case class NotFound(id: Long) extends PostError
case class SlugTaken(slug: String) extends PostError
case class ValidationFailed(errors: List[ValidationError]) extends PostError
case class DatabaseError(cause: Throwable) extends PostError
}
// Error recovery and mapping
def findOrCreate(slug: String, default: => Post): ZIO[PostService, Nothing, Post] =
ZIO.serviceWithZIO[PostService](_.findBySlug(slug))
.catchSome { case PostError.NotFound(_) =>
ZIO.serviceWithZIO[PostService](_.create(default))
}
.orDie // Defect if still fails
```
---
## Build Order
Based on dependencies, build in this order:
### Phase 1: Foundation (Parallel)
| Component | Dependencies | Effort |
|-----------|--------------|--------|
| Database Layer | None | Medium |
| Config Service | None | Low |
| Event Bus | None | Low |
### Phase 2: Core Services
| Component | Dependencies | Effort |
|-----------|--------------|--------|
| Plugin System | Config, Event Bus, Database | High |
### Phase 3: Domain Components
| Component | Dependencies | Effort |
|-----------|--------------|--------|
| Component System | Plugin System, Config | Medium |
| Auth Service | Database, Config | Medium |
### Phase 4: Rendering
| Component | Dependencies | Effort |
|-----------|--------------|--------|
| Theme Engine | Plugin System, Component System, Config | High |
| Template Parser | Theme Engine | Medium |
### Phase 5: HTTP Layer
| Component | Dependencies | Effort |
|-----------|--------------|--------|
| Request Pipeline | All above | Medium |
| Public Routes | Request Pipeline | Low |
| API Routes | Request Pipeline, Auth | Low |
### Phase 6: Admin
| Component | Dependencies | Effort |
|-----------|--------------|--------|
| Admin Backend | All above | High |
| Form Widget System | Admin Backend | High |
| List Widget System | Admin Backend | Medium |
### Phase 7: Core Plugins
| Component | Dependencies | Effort |
|-----------|--------------|--------|
| User Plugin | All core | High |
| Blog Plugin | All core | Medium |
| Pages Plugin | All core | Medium |
---
## WinterCMS Architecture Comparison
| Aspect | WinterCMS | SummerCMS |
|--------|-----------|-----------|
| **Language** | PHP (Laravel) | Scala (ZIO) |
| **Effect System** | None (exceptions) | ZIO effects (typed errors) |
| **DI** | Laravel Container | ZIO Layers |
| **HTTP** | Laravel Router | ZIO HTTP |
| **Database** | Eloquent ORM | ZIO Quill (compile-time) |
| **Templates** | Twig | ScalaTags + custom parser |
| **Events** | Laravel Events | ZIO Event Bus |
| **Config** | PHP arrays/YAML | YAML + ZIO Config |
| **Plugin Discovery** | Filesystem scan | Filesystem + SPI |
| **Model Extension** | Dynamic `extend()` | Type classes + events |
| **Frontend** | AJAX (Snowboard) | HTMX + Vue optional |
### Key Architectural Differences
1. **Type Safety:** SummerCMS gains compile-time query validation (Quill) and typed errors (ZIO) that WinterCMS lacks.
2. **Concurrency:** ZIO fibers enable true async/concurrent plugin loading and request handling vs PHP's request-per-process model.
3. **Resource Safety:** ZIO's `Scope` ensures database connections, file handles, and plugin resources are properly released even on errors.
4. **Model Extension:** WinterCMS uses runtime `extend()` calls. SummerCMS uses compile-time type classes for static extensions, plus events for runtime extensions.
5. **Testing:** ZIO's layer system enables true dependency injection for testing. Mock any layer by providing a test implementation.
---
## Sources
- [ZIO Architectural Patterns](https://zio.dev/reference/architecture/architectural-patterns/)
- [ZIO Service Pattern](https://zio.dev/reference/service-pattern/)
- [ZIO Layers](https://zio.dev/reference/contextual/zlayer/)
- [Structuring ZIO 2 Applications](https://softwaremill.com/structuring-zio-2-applications/)
- [ZIO HTTP Introduction](https://zio.dev/zio-http/)
- [ZIO HTTP HTMX Integration](https://index.scala-lang.org/zio/zio-http/artifacts/zio-http-htmx/3.3.0)
- [ZIO Quill Getting Started](https://zio.dev/zio-quill/getting-started/)
- [ZIO Schema](https://zio.dev/zio-schema/)
- [ZIO JSON](https://zio.dev/zio-json/)
- [ScalaTags Documentation](https://com-lihaoyi.github.io/scalatags/)
- [WinterCMS Plugin Extension](https://wintercms.com/docs/v1.2/docs/plugin/extending)
- [WinterCMS Plugin Registration](https://wintercms.com/docs/v1.2/docs/plugin/registration)
- [Tapir ZIO HTTP4s Integration](https://tapir.softwaremill.com/en/latest/server/zio-http4s.html)
- [Plugin Architecture Patterns](https://www.dotcms.com/blog/plugin-achitecture)