Back to Blog
Architecture March 09, 2026 | 11 min read

Umbrella Applications at Scale: Managing 94 Apps

Lessons from running one of the largest Elixir umbrella projects

Prismatic Engineering

Prismatic Platform

Why Umbrella?


The Prismatic Platform started as a single Phoenix application. As features grew

-- OSINT tools, due diligence pipelines, authentication, storage adapters,

telemetry, compliance engines -- the codebase became difficult to navigate and

test. Converting to an umbrella project provided clear domain boundaries while

keeping everything in a single repository.


Today the platform runs 94 applications in one umbrella, spanning domains

from cryptographic storage to AI agent orchestration.


Domain Boundary Design


Each umbrella app owns a specific domain. The naming convention signals the

domain clearly:


PrefixDomainExample Apps

|--------|--------|-------------|

prismatic_storage_*Storage adaptersprismatic_storage_ecto, prismatic_storage_ets prismatic_osint_*Intelligence toolsprismatic_osint_core, prismatic_osint_sources prismatic_*_webWeb interfacesprismatic_web, prismatic_perimeter_web prismatic_dd*Due diligenceprismatic_dd, prismatic_dd_dataroom

The key rule: apps depend downward, never upward. Storage apps never depend

on web apps. Core business logic never depends on presentation. This is enforced

by the dependency graph in each app's mix.exs.


Dependency Management


With 94 apps, dependency conflicts are a real risk. The platform uses several

strategies:


Shared dependencies in root mix.exs: Common dependencies like jason,

telemetry, and plug are declared in the root project to ensure version

consistency.


Explicit inter-app dependencies: Each app declares exactly which sibling

apps it depends on. This makes the dependency graph explicit and auditable:



# apps/prismatic_dd/mix.exs

defp deps do

[

{:prismatic, in_umbrella: true},

{:prismatic_storage_core, in_umbrella: true},

{:prismatic_osint_core, in_umbrella: true}

]

end


The DEPS doctrine: Version constraints are mandatory. No ~> 0.0 or git

dependencies pointing to unstable branches. Override dependencies require

written justification in comments.


Cross-App Communication


Apps communicate through three mechanisms:


1. Direct Function Calls


When app A depends on app B, it calls B's public API directly. This is the

simplest and most common pattern:



# From prismatic_dd, calling prismatic_osint_core

PrismaticOsintCore.search(:czech_ares, %{ico: "12345678"})


2. Phoenix PubSub


For decoupled, event-driven communication, apps publish and subscribe to topics

without direct dependencies:



# Publisher (prismatic_dd)

Phoenix.PubSub.broadcast(Prismatic.PubSub, "dd:pipeline", {:entity_loaded, entity})


# Subscriber (prismatic_web) - no dependency on prismatic_dd

Phoenix.PubSub.subscribe(Prismatic.PubSub, "dd:pipeline")


3. Storage Traits


The prismatic_storage_core app defines behaviour callbacks that storage

adapters implement. Any app can use storage without knowing which adapter

is active:



PrismaticStorageCore.store(:default, key, value)

PrismaticStorageCore.fetch(:default, key)


Compilation and Testing at Scale


Compiling 94 apps takes time. The platform uses several optimizations:


  • Incremental compilation: Only recompile changed apps and their dependents
  • Parallel compilation: mix compile compiles independent apps in parallel
  • Targeted testing: mix test apps/prismatic_dd runs only one app's tests
  • Changed-file testing: Pre-commit hooks identify changed apps and test only those

  • The full test suite (6,330 test files) takes several minutes to run. CI runs the

    full suite; local development uses targeted testing.


    Challenges and Solutions


    Challenge: Circular Dependencies


    Two apps that depend on each other create a compilation deadlock. The solution is

    to extract the shared interface into a third app. The platform has several

    -core apps (prismatic_osint_core, prismatic_storage_core) that exist

    solely to break cycles.


    Challenge: Configuration Sprawl


    With 94 apps, config/config.exs could become enormous. Each app owns its own

    configuration namespace, and the root config only sets cross-cutting concerns

    like logger level and PubSub configuration.


    Challenge: Knowing Where Code Lives


    New developers struggle to find code in 94 apps. The platform addresses this with

    the RDME doctrine (every app has a README), the Knowledge Index (21,000+ document

    cross-reference), and the /agents command that routes questions to the right

    domain expert.


    Is Umbrella Right for You?


    Umbrella projects shine when you have clear domain boundaries, a single team

    (or tightly coordinated teams), and shared infrastructure. They struggle when

    apps need independent deployment schedules or when teams cannot coordinate

    releases. For the Prismatic Platform, the umbrella structure has been a net

    positive, enabling code reuse and consistent quality enforcement across all

    94 applications.


    Tags

    umbrella elixir monorepo architecture otp