Beamtalk Domain-Driven Design Model

Status: Draft - Comprehensive domain model for compiler and runtime

This document presents a domain-driven design (DDD) analysis of the Beamtalk compiler and runtime system, identifying core domains, bounded contexts, domain entities, value objects, aggregates, and ubiquitous language.


Table of Contents


Overview

Beamtalk is a live, interactive Smalltalk-like language for the BEAM VM that brings Smalltalk's legendary live programming experience to Erlang's battle-tested runtime. While inspired by Smalltalk's syntax and philosophy, Beamtalk makes pragmatic choices for BEAM compatibility (see Syntax Rationale). The system comprises two major subsystems:

  1. Compiler (Rust) - Compiles .bt source to BEAM bytecode via Core Erlang
  2. Runtime (Erlang) - Provides actor execution, futures, class registry, and REPL

The DDD model helps us understand the distinct problem spaces, maintain clear boundaries, and establish a common vocabulary across the team.


Strategic Design

Core Domains

1. Live Programming Domain (Core - Differentiator)

What it does: Enables continuous modification of running code without restarts

Why it matters: This is Beamtalk's unique value proposition - Smalltalk-style liveness on BEAM

Subdomains:

Strategic importance: This is what makes Beamtalk different from Gleam, Elixir, or LFE.

2. Actor-Based Execution Domain (Core - Essential)

What it does: Maps Smalltalk objects to BEAM processes with async message passing

Why it matters: Bridges Smalltalk's object model with BEAM's process model

Subdomains:

3. Language Compilation Domain (Supporting - Complex but not differentiating)

What it does: Transforms Beamtalk source code into executable BEAM bytecode

Why it matters: Required for the system to work, but not what makes Beamtalk special

Subdomains:

4. Developer Tooling Domain (Supporting - Enables adoption)

What it does: Provides IDE integration, completions, diagnostics, hover info

Why it matters: Modern developers expect excellent tooling

Subdomains:

Bounded Contexts

A bounded context is an explicit boundary within which a domain model is defined and applicable. Each context has its own ubiquitous language, and terms may mean different things across contexts.

┌─────────────────────────────────────────────────────────────────────┐
│                         DEVELOPER MACHINE                           │
│                                                                     │
│  ┌───────────────────────────────────────────────────────────────┐  │
│  │              LANGUAGE SERVICE CONTEXT (Rust)                   │  │
│  │  - Queries: Completions, Hover, Diagnostics                   │  │
│  │  - Incremental: File cache, Query cache                       │  │
│  │  - IDE Integration: LSP Protocol                              │  │
│  └───────────────────────────────────────────────────────────────┘  │
│                              ▲                                      │
│                              │ uses                                 │
│  ┌───────────────────────────┴───────────────────────────────────┐  │
│  │              COMPILER CONTEXT (Rust)                           │  │
│  │  ┌────────────────┐  ┌────────────────┐  ┌─────────────────┐ │   │
│  │  │ SOURCE         │→ │ SEMANTIC       │→ │ CODE            │ │   │
│  │  │ ANALYSIS       │  │ ANALYSIS       │  │ GENERATION      │ │   │
│  │  │ (Lexer/Parser) │  │ (Type/Name)    │  │ (Core Erlang)   │ │   │
│  │  └────────────────┘  └────────────────┘  └─────────────────┘ │   │
│  └───────────────────────────────────────────────────────────────┘  │
│                              │ produces                             │
│                              ▼                                      │
│                      [ .beam bytecode ]                             │
└──────────────────────────────┼──────────────────────────────────────┘
                                │ hot loads into
                                ▼
┌─────────────────────────────────────────────────────────────────────┐
│                         BEAM RUNTIME                                │
│                                                                     │
│  ┌───────────────────────────────────────────────────────────────┐  │
│  │              OBJECT SYSTEM CONTEXT (Erlang)                    │ │
│  │  - Class Registry: Global class metadata                      │  │
│  │  - Instance Tracking: ETS-based instance registry             │  │
│  │  - Method Dispatch: Super dispatch, DNU handling              │  │
│  └───────────────────────────────────────────────────────────────┘  │
│                              ▲                                      │
│                              │ uses                                 │
│  ┌───────────────────────────┴───────────────────────────────────┐  │
│  │              ACTOR SYSTEM CONTEXT (Erlang)                     │ │
│  │  - Actor Lifecycle: spawn, init, terminate                     │ │
│  │  - Message Dispatch: async/sync routing                       │  │
│  │  - State Management: Field maps with migration                │  │
│  └───────────────────────────────────────────────────────────────┘  │
│                              ▲                                      │
│                              │ uses                                 │
│  ┌───────────────────────────┴───────────────────────────────────┐  │
│  │              CONCURRENCY CONTEXT (Erlang)                      │ │
│  │  - Future/Promise: Async result handling                       │ │
│  │  - Process Monitors: Lifecycle tracking                        │ │
│  │  - Supervision: Restart strategies                             │ │
│  └───────────────────────────────────────────────────────────────┘  │
│                                                                     │
│  ┌───────────────────────────────────────────────────────────────┐  │
│  │              HOT RELOAD CONTEXT (Erlang)                       │ │
│  │  - Code Loading: BEAM code upgrade                            │  │
│  │  - State Migration: code_change/3 callbacks                   │  │
│  │  - Version Coexistence: Old/new code                          │  │
│  └───────────────────────────────────────────────────────────────┘  │
│                                                                     │
│  ┌───────────────────────────────────────────────────────────────┐  │
│  │              WORKSPACE CONTEXT (Erlang)                        │ │
│  │  - Lifecycle: detached BEAM node, idle cleanup                │  │
│  │  - Supervision: ActorSupervisor, SessionSupervisor            │  │
│  │  - Metadata: workspace_id, project_path, last_activity        │  │
│  └───────────────────────────────────────────────────────────────┘  │
│                              ▲                                      │
│                              │ hosts                                │
│  ┌───────────────────────────┴───────────────────────────────────┐  │
│  │              REPL SESSION CONTEXT (Erlang)                     │ │
│  │  - Expression Evaluation: On-demand compilation               │  │
│  │  - Binding Management: Per-session variable state             │  │
│  │  - Protocol: WebSocket JSON (nREPL-inspired)                  │  │
│  └───────────────────────────────────────────────────────────────┘  │
│                                                                     │
│  ┌───────────────────────────────────────────────────────────────┐  │
│  │              BEAMTALK GLOBAL CONTEXT (Erlang)                  │ │
│  │  - Façade over Workspace + Object System for user code        │  │
│  │  - Runtime introspection: actors, modules, sessions           │  │
│  │  - Project metadata: version, nodeName, projectPath           │  │
│  └───────────────────────────────────────────────────────────────┘  │
└─────────────────────────────────────────────────────────────────────┘

Context Map

The context map shows how bounded contexts relate and communicate:

Upstream ContextDownstream ContextRelationshipIntegration
SOURCE ANALYSISSEMANTIC ANALYSISCustomer-SupplierAST + Spans
SEMANTIC ANALYSISCODE GENERATIONCustomer-SupplierTyped AST
CODE GENERATIONACTOR SYSTEMPublished LanguageCore Erlang → BEAM
COMPILERLANGUAGE SERVICEShared KernelAST, Spans, Errors
OBJECT SYSTEMACTOR SYSTEMConformistCalls class registry APIs
CONCURRENCYACTOR SYSTEMPartnershipBidirectional dependencies
HOT RELOADACTOR SYSTEMCustomer-SupplierTriggers state migration
ACTOR SYSTEMREPL SESSIONAnti-Corruption LayerREPL wraps raw gen_server
WORKSPACEREPL SESSIONCustomer-SupplierWorkspace provides actor sup, module registry
WORKSPACEHOT RELOADCustomer-SupplierWorkspace triggers code upgrades via loader
WORKSPACEOBJECT SYSTEMCustomer-SupplierWorkspace reads class registry, dispatches methods, reflects on fields
BEAMTALK GLOBALWORKSPACEFaçadeSingle entry point exposing workspace state to user code

Key relationships:


Ubiquitous Language

A shared vocabulary used consistently across team, code, and documentation. Terms have precise meanings within their bounded context.

Compiler Domain Terms

TermDefinitionContext
SpanSource code location (byte offset range)SOURCE ANALYSIS
TokenLexical unit with type and spanSOURCE ANALYSIS
AST NodeAbstract syntax tree element with spanSOURCE ANALYSIS
ExpressionAST node that evaluates to a valueSOURCE ANALYSIS
IdentifierNamed reference (variable, class, method)SOURCE ANALYSIS
SelectorMessage name (unary, binary, keyword)SOURCE ANALYSIS
BindingVariable name to value associationSEMANTIC ANALYSIS
ScopeNaming context (local, instance, class)SEMANTIC ANALYSIS
Type AnnotationOptional type constraint on identifierSEMANTIC ANALYSIS
Core ErlangIntermediate representation (IR)CODE GENERATION
ModuleCompilation unit (file containing classes/expressions)All COMPILER
DiagnosticError, warning, or info message with spanLANGUAGE SERVICE
QueryRequest for IDE information (hover, completion)LANGUAGE SERVICE
CompletionSuggested code at cursor positionLANGUAGE SERVICE

Runtime Domain Terms

TermDefinitionContext
ActorBEAM process representing a Beamtalk objectACTOR SYSTEM
Message SendAsync or sync communication between actorsACTOR SYSTEM
SelectorMethod identifier (atom like increment or 'at:put:')ACTOR SYSTEM
State MapActor's field storage (Erlang map)ACTOR SYSTEM
SelfActor's own reference (#beamtalk_object record)ACTOR SYSTEM
MethodFunction implementing message handlerACTOR SYSTEM
doesNotUnderstandCatch-all handler for unknown messagesACTOR SYSTEM
FutureAsync result handle (BEAM process)CONCURRENCY
Promise(Synonym for Future)CONCURRENCY
AwaitBlocking wait for future resultCONCURRENCY
ResolveComplete future with success valueCONCURRENCY
RejectComplete future with error reasonCONCURRENCY
CallbackFunction executed on future completionCONCURRENCY
ClassTemplate defining actor behaviorOBJECT SYSTEM
InstanceRunning actor (gen_server process)OBJECT SYSTEM
SuperclassParent class in inheritance hierarchyOBJECT SYSTEM
Method DispatchRouting selector to implementationOBJECT SYSTEM
Super DispatchInvoke superclass method implementationOBJECT SYSTEM
Class RegistryGlobal map of class metadataOBJECT SYSTEM
Instance TrackingETS table of all live instancesOBJECT SYSTEM
Hot ReloadLoading new code into running systemHOT RELOAD
Code UpgradeBEAM's hot swap mechanismHOT RELOAD
State MigrationTransforming state for new code versionHOT RELOAD
code_change/3OTP callback for state migrationHOT RELOAD
REPLRead-Eval-Print LoopREPL SESSION
BindingVariable name to value association (session-local, not persisted across reconnects)REPL SESSION
EvaluationCompile + load + execute expression in REPL sessionREPL SESSION
SessionSingle REPL connection with its own bindings and eval counter; ephemeralREPL SESSION
EvalRequestProtocol message requesting expression evaluationREPL SESSION
EvalResponseProtocol message with result value, stdout output, warnings, statusREPL SESSION
ProtocolMessageJSON WebSocket frame following nREPL-inspired op/id/session schemaREPL SESSION
WorkspaceLong-lived detached BEAM node providing shared actors, modules, metadataWORKSPACE
WorkspaceIdUnique identifier for a workspace (SHA256 of project path or explicit name)WORKSPACE
ProjectPathFilesystem path of the project this workspace belongs toWORKSPACE
IdleTimeoutInactivity period after which a workspace self-terminates (default 4 hours)WORKSPACE
ActivityTimestampUnix timestamp of last REPL, actor, or code-reload activity in the workspaceWORKSPACE
Beamtalk GlobalRuntime global object (analogous to Smalltalk) accessible in all sessionsBEAMTALK GLOBAL

Shared Terms (Cross-Context)

TermDefinitionUsage
Class DefinitionDeclaration of actor templateCOMPILER → RUNTIME
Method DefinitionDeclaration of message handlerCOMPILER → RUNTIME
State DeclarationInstance variable definitionCOMPILER → RUNTIME
Beamtalk ObjectRecord bundling class + pidRUNTIME (cross-context)

Compiler Domain Model

Source Analysis Context

Purpose: Transform raw text into structured AST

Aggregates:

1. Module (Aggregate Root)

Invariants:

Entities:

Value Objects:

Repositories:

Domain Services:

Key Patterns:

Example Domain Logic:

// Parsing maintains span invariants
impl Parser {
    fn parse_expression(&mut self) -> Expression {
        let start = self.current_token().span.start;
        let expr = self.parse_primary();
        let end = self.previous_token().span.end;
        expr.with_span(Span::new(start, end)) // Span encapsulates child spans
    }
}

Semantic Analysis Context

Purpose: Resolve names, check types, validate semantics

Aggregates:

1. Scope (Aggregate Root)

Invariants:

Entities:

Value Objects:

Repositories:

Domain Services:

Key Patterns:

Example Domain Logic:

impl NameResolver {
    fn resolve(&self, name: &Identifier, scope: &Scope) -> Option<Binding> {
        // Try current scope
        if let Some(binding) = scope.get(name) {
            return Some(binding.clone());
        }
        // Walk parent chain
        scope.parent.as_ref().and_then(|p| self.resolve(name, p))
    }
}

impl BlockContextClassifier {
    fn classify(&self, block_span: Span, parent: &Expression, in_assignment: bool) -> BlockContext {
        // Stored: block on RHS of assignment
        if in_assignment {
            return BlockContext::Stored;
        }
        // ControlFlow: literal block in control flow selector
        // Passed: block variable in any argument position
        // Other: return value, nested blocks, etc.
        // (see block_context.rs for full implementation)
    }
}

Code Generation Context

Purpose: Transform AST into executable Core Erlang

Class Kind Routing (see ADR 0007):

The code generator routes class definitions through one of three paths based on class kind:

Class KindRoutingGenerated Code
Actorgenerate_actor_module()gen_server with init/1, handle_call/3, handle_cast/3, spawn/0
Value Typegenerate_value_type_module()Map-backed with new/0, new/1, pure function methods
Primitive Typegenerate_primitive_module()Method table only — no new, no state management

Class kind is determined from compiled stdlib metadata: Primitive Types match a known set (Integer, String, etc.), Actors inherit from Actor, everything else is a Value Type.

Aggregates:

1. CompilationUnit (Aggregate Root)

Invariants:

Entities:

Value Objects:

Factories:

Domain Services:

Key Patterns:

Example Domain Logic:

impl CoreErlangGenerator {
    fn generate_async_send(&self, receiver: Expr, selector: Selector, args: Vec<Expr>) -> ErlangExpr {
        // Future creation
        let future = ErlangExpr::call("beamtalk_future", "new", vec![]);
        
        // Extract pid from #beamtalk_object
        let pid = ErlangExpr::call("erlang", "element", vec![
            ErlangExpr::literal(4), // pid is 4th field
            receiver.clone(),
        ]);
        
        // Async cast
        let cast = ErlangExpr::call("gen_server", "cast", vec![
            pid,
            ErlangExpr::tuple(vec![
                ErlangExpr::atom(selector.to_atom()),
                ErlangExpr::list(args),
                future.clone(),
            ]),
        ]);
        
        // Return future
        ErlangExpr::let_binding("Future", future, ErlangExpr::seq(vec![cast, future]))
    }
}

Standard Library Context

Purpose: Compile stdlib/src/*.bt files into class metadata and BEAM modules via pragma-based primitive injection

DDD Context: Standard Library (see ADR 0007)

Key Concepts:

TermDefinition
PragmaIn-body annotation (@primitive name) declaring a method's implementation
Named IntrinsicEntry in compiler's finite registry mapping name → code generation function
Primitive TypeClass backed by native Erlang value (Integer, String, etc.) — method table only, no constructor
Runtime Dispatch ModuleErlang module (e.g., beamtalk_integer.erl) providing type checking, structured errors, extension registry

Relationships:

Domain Services:

Key Invariant: Every intrinsic name must resolve to exactly one code generation function. Unknown names are compile errors.

Language Service Context

Purpose: Answer IDE queries incrementally

Aggregates:

1. LanguageService (Aggregate Root)

Invariants:

Entities:

Value Objects: (defined in language_service/value_objects.rs)

Repositories:

2. ProjectIndex (Aggregate Root)

Purpose: Cross-file class hierarchy and symbol index (ADR 0024, Phase 1)

Invariants:

Value Objects:

Key Operations:

Domain Services:

Key Patterns:

Example Domain Logic:

impl LanguageService {
    pub fn completions(&mut self, path: &Path, position: Position) -> Vec<Completion> {
        // Get or parse file
        let file = self.get_or_parse_file(path);
        
        // Find AST node at position
        let node = file.module.node_at_position(position)?;
        
        // Scope-aware completions
        let scope = self.scope_at_position(&file.module, position);
        scope.bindings().iter()
            .map(|b| Completion {
                label: b.name.clone(),
                kind: CompletionKind::Variable,
                detail: Some(b.type_.to_string()),
                insert_text: b.name.clone(),
            })
            .collect()
    }
}

Runtime Domain Model

Actor System Context

Purpose: Execute Beamtalk actors as BEAM processes

Aggregates:

1. Actor (Aggregate Root)

Invariants:

Entities:

Value Objects:

Factories:

Repositories:

Domain Services:

Key Patterns:

Example Domain Logic:

%% Message dispatch with error isolation (from Flavors)
dispatch(Selector, Args, Self, State) ->
    Methods = maps:get('__methods__', State),
    case maps:find(Selector, Methods) of
        {ok, Fun} ->
            try
                {Result, NewState} = Fun(Self, State, Args),
                {reply, Result, NewState}
            catch
                error:Error -> {error, {error, Error}, State};
                exit:Exit -> {error, {exit, Exit}, State};
                throw:Thrown -> {error, {throw, Thrown}, State}
            end;
        error ->
            handle_dnu(Selector, Args, Self, State)
    end.

Concurrency Context

Purpose: Manage async communication via futures

Aggregates:

1. Future (Aggregate Root)

Invariants:

State Machine:

    ┌─────────┐  resolve(Value)   ┌──────────┐
    │ pending │──────────────────→ │ resolved │─→ terminates
    └─────────┘                    └──────────┘   (5 min idle)
         │
         │ reject(Reason)
         ▼
    ┌──────────┐
    │ rejected │─→ terminates
    └──────────┘   (5 min idle)

Entities:

Value Objects:

Repositories:

Domain Services:

Key Patterns:

Example Domain Logic:

%% Future state machine
pending(Waiters) ->
    receive
        {resolve, Value} ->
            notify_waiters(Waiters, resolved, Value),
            resolved(Value);
        {await, Pid} ->
            pending([{await, Pid, infinity} | Waiters]);
        {add_callback, resolved, Callback} ->
            pending([{callback, resolved, Callback} | Waiters])
    end.

resolved(Value) ->
    receive
        {await, Pid} ->
            Pid ! {future_resolved, self(), Value},
            resolved(Value);
        {add_callback, resolved, Callback} ->
            Callback(Value),
            resolved(Value)
    after 300000 -> % 5 minutes
        ok  % Terminate to prevent leaks
    end.

Object System Context

Purpose: Manage class metadata and instance tracking

Aggregates:

1. ClassRegistry (Aggregate Root)

Invariants:

Entities:

Value Objects:

Repositories:

Domain Services:

Key Patterns:

Example Domain Logic:

%% Super method dispatch - walks inheritance chain
find_and_invoke_super_method(ServerRef, Superclass, Selector, Args, State) ->
    case lookup(ServerRef, Superclass) of
        {ok, ClassInfo} ->
            Methods = maps:get(methods, ClassInfo),
            case maps:find(Selector, Methods) of
                {ok, MethodInfo} ->
                    invoke_method(MethodInfo, Args, State);
                error ->
                    %% Not in this class, try its superclass
                    case maps:get(superclass, ClassInfo) of
                        none -> {error, {method_not_found, Superclass, Selector}};
                        NextSuper -> find_and_invoke_super_method(ServerRef, NextSuper, Selector, Args, State)
                    end
            end;
        undefined ->
            {error, {class_not_found, Superclass}}
    end.

Hot Reload Context

Purpose: Update running code without losing state

Aggregates:

1. CodeUpgrade (Aggregate Root)

Invariants:

Entities:

Value Objects:

Repositories:

Domain Services:

Key Patterns:

Example Domain Logic:

%% Domain service: beamtalk_hot_reload
%% Centralizes code_change/3 callback logic for all gen_server behaviors
code_change(OldVsn, OldState, Extra) ->
    %% Current implementation: preserve state unchanged
    %% Future: automatic field migration as shown below
    {ok, OldState}.

%% Future implementation (when field defaults are stored in class registry):
%% code_change(OldVsn, OldState, Extra) ->
%%     %% Get new field defaults from class metadata
%%     Class = maps:get('__class__', OldState),
%%     {ok, ClassInfo} = beamtalk_classes:lookup(Class),
%%     DefaultFields = maps:get(default_fields, ClassInfo),
%%     
%%     %% Merge: new defaults + existing fields (existing take precedence)
%%     NewState = maps:merge(DefaultFields, OldState),
%%     
%%     %% Call user-defined migration if present
%%     case maps:find('__migrate__', maps:get('__methods__', NewState, #{})) of
%%         {ok, MigrateFun} ->
%%             {ok, MigrateFun(OldVsn, NewState, Extra)};
%%         error ->
%%             {ok, NewState}
%%     end.

Workspace Context

Purpose: Manage the lifecycle and shared state of a long-lived detached BEAM node (the "workspace") that survives REPL disconnects, providing persistent actors, loaded modules, and project metadata to all connected sessions.

Aggregates:

1. WorkspaceMeta (Aggregate Root)

Invariants:

Entities:

Value Objects:

Repositories:

Domain Services:

Key Patterns:

Runtime Boundary (Customer-Supplier with Object System & Hot Reload):

Workspace is a customer of the Object System context and the Hot Reload context. All cross-context calls from workspace source files to runtime modules are routed exclusively through beamtalk_runtime_api (implemented in BT-1106) — the sole approved entry point. Workspace source files must never call internal runtime modules directly.

See docs/development/erlang-guidelines.md § Approved Cross-Context API for the full function mapping table.

Example Domain Logic:

%% Domain service: beamtalk_idle_monitor
%% Periodically checks idle time and self-terminates
handle_info(check_idle, #state{enabled = true, max_idle_seconds = Max} = State) ->
    LastActivity = beamtalk_workspace_meta:get_last_activity(),
    IdleSeconds = erlang:system_time(second) - LastActivity,
    case IdleSeconds > Max of
        true ->
            ?LOG_INFO("Workspace idle for ~p seconds, shutting down", [IdleSeconds]),
            init:stop();
        false ->
            ok
    end,
    TRef = erlang:send_after(?CHECK_INTERVAL, self(), check_idle),
    {noreply, State#state{timer_ref = TRef}}.

%% Aggregate root: beamtalk_workspace_meta
%% Tracks supervised actors via process monitors for automatic cleanup
register_actor(Pid) ->
    MonRef = erlang:monitor(process, Pid),
    gen_server:call(?MODULE, {register_actor, Pid, MonRef}).

handle_info({'DOWN', _MonRef, process, Pid, _Reason}, State) ->
    NewActors = lists:delete(Pid, State#state.supervised_actors),
    {noreply, State#state{supervised_actors = NewActors}}.

REPL Session Context

Purpose: Manage a single REPL client connection — evaluate expressions, maintain per-session variable bindings, and translate protocol messages to/from runtime operations.

Aggregates:

1. Session (Aggregate Root)

Invariants:

Entities:

Value Objects:

Repositories:

Domain Services:

Operation Handlers (Domain Services):

Key Patterns:

Example Domain Logic:

%% Domain service: beamtalk_repl_eval
%% Core evaluation pipeline: compile → load → execute → capture result
do_eval(Expression, State, Subscriber) ->
    Counter = beamtalk_repl_state:get_eval_counter(State),
    ModuleName = list_to_atom("beamtalk_repl_eval_" ++ integer_to_list(Counter)),
    NewState = beamtalk_repl_state:increment_eval_counter(State),
    Bindings = beamtalk_repl_state:get_bindings(State),
    case compile_expression(Expression, ModuleName, Bindings) of
        {ok, class_definition, ClassInfo, Warnings} ->
            handle_class_definition(ClassInfo, Warnings, Expression, NewState);
        {ok, Beam, NewBindings, Warnings} ->
            {Output, Result} = beamtalk_io_capture:capture(Subscriber, fun() ->
                activate_module(ModuleName, Beam),
                ModuleName:eval()
            end),
            FinalState = beamtalk_repl_state:set_bindings(NewState, NewBindings),
            {ok, Result, Output, Warnings, FinalState};
        {error, Reason, Warnings} ->
            {error, Reason, <<>>, Warnings, NewState}
    end.

Beamtalk Global Context

Purpose: Expose a stable, Smalltalk-inspired global object (Beamtalk) that gives user code and REPL sessions a single entry point for runtime introspection — listing actors, modules, classes, and sessions — without exposing raw Erlang APIs.

Analogy: Beamtalk is to Beamtalk what Smalltalk is to Pharo: a globally accessible system dictionary and runtime façade.

Aggregates:

1. BeamtalkGlobal (Singleton Façade)

Invariants:

Entities:

Value Objects:

Domain Services:

Key Operations (available in all REPL sessions):

MessageReturnDelegates To
Beamtalk allClassesList of class namesbeamtalk_class_registry
Beamtalk classNamed: #CounterClass metadatabeamtalk_class_registry
Beamtalk actorsList of running actorsbeamtalk_workspace_meta
Beamtalk modulesList of loaded modulesbeamtalk_workspace_meta
Beamtalk versionVersion stringRuntime app env
Beamtalk nodeNameBEAM node atomerlang:node()
Beamtalk projectPathProject path binarybeamtalk_workspace_meta

Key Patterns:

Example Domain Logic:

// User code in any REPL session
Beamtalk allClasses          // => #('Counter', 'Logger', 'Actor', 'Object', ...)
Beamtalk classNamed: #Counter // => <ClassInfo for Counter>
Beamtalk actors               // => #(<0.123.0> Counter, <0.124.0> Logger)
Beamtalk version              // => '0.3.1'
%% Erlang implementation delegates to workspace context
beamtalk_actors() ->
    case beamtalk_workspace_meta:supervised_actors() of
        Pids when is_list(Pids) ->
            [{Pid, beamtalk_class_registry:class_of(Pid)} || Pid <- Pids, is_process_alive(Pid)];
        _ ->
            []
    end.

Cross-Cutting Concerns

These concerns span multiple bounded contexts:

1. Error Handling

Strategy: Different approaches per context

ContextStrategyRationale
COMPILERResult types (Result<T, E>)Rust idiomatic, explicit error handling
LANGUAGE SERVICEDiagnostic collectionIDE needs all errors, not just first
ACTOR SYSTEMError isolation + rejectionActors don't crash, errors returned to caller
FUTURERejection stateAsync errors propagate via future rejection

Domain Service: ErrorFormatter - consistent error messages across contexts

2. Logging and Observability

Strategy: Context-specific logging

ContextMechanismWhat to Log
COMPILERtracing crateParse errors, codegen steps
LANGUAGE SERVICEtracingQuery times, cache hits/misses
ACTOR SYSTEMerror_loggerActor crashes, DNU calls
HOT RELOADerror_loggerCode upgrades, migration failures

Domain Event: CodeUpgradeCompleted - notify observers of successful reload

3. Performance Monitoring

Critical Paths:

Metrics:

4. Testing Strategy

Per-Context Testing:

ContextTest TypeFocus
SOURCE ANALYSISProperty testsParse roundtrip, error recovery
SEMANTIC ANALYSISExample testsName resolution, type checking
CODE GENERATIONSnapshot testsCore Erlang output stability
ACTOR SYSTEMUnit tests (EUnit)Message dispatch, error isolation
CONCURRENCYProperty tests (PropEr)Future state machine invariants
HOT RELOADIntegration testsState migration correctness

Snapshot Testing: test-package-compiler/ directory contains expected Core Erlang outputs


Domain Events

Domain events represent significant occurrences in the system. They enable loose coupling between bounded contexts.

Compiler Domain Events

EventPayloadTriggered WhenSubscribers
FileParsedModule, DiagnosticsParse completesLANGUAGE SERVICE
TypeCheckCompletedModule, ErrorsType checking doneLANGUAGE SERVICE
CodeGeneratedModuleName, CoreErlangCode generation completesHOT RELOAD
CompilationFailedPath, ErrorsCompilation failsLANGUAGE SERVICE

Runtime Domain Events

EventPayloadTriggered WhenSubscribers
ActorSpawnedClass, PidActor createdINSTANCE TRACKING
ActorTerminatedClass, Pid, ReasonActor diesINSTANCE TRACKING
MessageDispatchedSelector, ResultMessage handled(Tracing/debugging)
FutureResolvedFuturePid, ValueFuture completesWaiters
FutureRejectedFuturePid, ReasonFuture failsWaiters
CodeUpgradeStartedModule, VersionHot reload beginsActor instances
CodeUpgradeCompletedModule, StatsHot reload succeedsMonitoring
StateMigrationFailedPid, Reasoncode_change/3 failsSupervision
SessionStartedSessionId, PidWebSocket authenticatedWORKSPACE (update activity)
SessionClosedSessionId, ReasonWebSocket closesWORKSPACE (cleanup session sup)
WorkspaceStartedWorkspaceId, ProjectPath, PortNode startup completeMonitoring
WorkspaceStoppedWorkspaceId, Reasoninit:stop/0 calledMonitoring
WorkspaceIdleTimeoutWorkspaceId, IdleSecondsIdle limit exceeded(triggers WorkspaceStopped)
ModuleLoadedModuleName, SourcePathload-file or load-source opWORKSPACE (register module)
ModuleUnloadedModuleNameunload op or class redefinitionWORKSPACE (deregister module)

Event Flow Example: File Save → Hot Reload

1. IDE saves file
2. LANGUAGE SERVICEFileParsed event
3. COMPILERCodeGenerated event
4. HOT RELOAD receives event
5. HOT RELOADCodeUpgradeStarted event
6. Actors receive code_change/3
7. HOT RELOADCodeUpgradeCompleted event

Architecture Decision Records

ADR-1: Rust for Compiler, Erlang for Runtime

Context: Need to choose languages for compiler and runtime subsystems

Decision:

Rationale:

Status: Accepted

ADR-2: Two Modules per Class (Flavors Pattern)

Context: Need to decide module generation strategy

Decision: Generate two modules per class:

Rationale:

Status: Accepted

ADR-3: Process-per-Future

Context: Need async result representation for async-first message sending

Decision: Each future is a lightweight BEAM process (~2KB)

Alternatives Considered:

Rationale:

Status: Accepted

ADR-4: Error Isolation in Actors (Flavors Pattern)

Context: Should actor crashes propagate to caller?

Decision: Actors catch errors and return them to caller via future rejection or error tuple

Rationale:

Status: Accepted

ADR-5: ETS-based Instance Tracking

Context: Need to implement Class allInstances for introspection

Decision: Use ETS bag table with process monitors for auto-cleanup

Alternatives Considered:

Rationale:

Status: Accepted

ADR-6: Pragmatic Hybrid Object Model

Context: How to map Smalltalk's object model to BEAM?

Decision: Embrace BEAM's actor model, reify what's natural, document limitations

Alternatives Considered:

Rationale:

Status: Implemented (see ADR 0005)

ADR-7: Compiler as Language Service

Context: How to architect compiler for IDE integration?

Decision: TypeScript approach - compiler IS the language service, not separate

Rationale:

Status: Accepted (see Principle 13 in principles.html)


Summary

This DDD model provides:

  1. Strategic Clarity: Core vs. supporting domains, bounded contexts, context map
  2. Ubiquitous Language: Shared vocabulary across team, code, and docs
  3. Domain Models: Aggregates, entities, value objects, services per context
  4. Event-Driven Integration: Domain events for loose coupling
  5. Architectural Decisions: Rationale for key design choices

Key Insights:

Next Steps:

  1. Map existing code to bounded contexts (identify misalignments)
  2. Establish context integration tests (validate anti-corruption layers)
  3. Define domain events schema (enable event sourcing/auditing)
  4. Document aggregate invariants in code (runtime assertions)
  5. Refine ubiquitous language (update team communication)

References