# 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)