Zum Hauptinhalt springen
Echtzeit-Radar & Feeds
Alle RSS Feeds ➔
👥 Community & Social
Windows Tipps & SecurityGrafikkarte vor Überhitzung schützen: So geht’s(25.09.2026 um 08:00 Uhr)
••••••••••
Windows Tipps & SecurityGrafikkarte vor Überhitzung schützen: So geht’s(25.09.2026 um 08:00 Uhr)
••••••••••
Intelligence View
⚡ tsecurity.de Intelligence

FCIS — Functional Core, Imperative Shell

OVERVIEW 💡 A pattern that splits your codebase into two hard zones: ZONE LOCATION RULE Functional Core src/core/ Pure functions only. No I/O. No side effects. Imperative Shell src/shell/ All I/O lives here. Calls core to make d…

0
↗ Quelle (dev.to)
Reagiere als Erste:r — dein Feedback zählt!

OVERVIEW




💡 A pattern that splits your codebase into two hard zones:
























ZONE LOCATION RULE
Functional Core src/core/ Pure functions only. No I/O. No side effects.
Imperative Shell src/shell/ All I/O lives here. Calls core to make decisions.



💡 Shell (IMPURE) fetches data, asks the core what to do, then acts on the answer.





flowchart TB
subgraph SHELL[" src/shell/ - Imperative Shell "]
direction TB
CLI["CLI commands"]
subgraph STEPS[" 5-Step Handler Pattern "]
P["1. PARSE"]
F["2. FETCH"]
C["3. CALL CORE"]
A["4. ACT"]
O["5. OUTPUT"]
P --> F --> C --> A --> O
end
DB["DB queries"]
PRINTER["view.printer"]
CLI --> STEPS
STEPS --> DB
STEPS --> PRINTER
end
classDef shell fill:#0d47a1,stroke:#1565c0,color:#e3f2fd
classDef steps fill:#37474f,stroke:#546e7a,color:#eceff1
class SHELL,CLI,DB,PRINTER shell
class P,F,C,A,O steps







💡 (Core PURE) never imports from shell. It calls core with data → gets Result back.





flowchart TB
subgraph CORE[" src/core/ - Functional Core "]
direction TB
TYPES["types.ts"]
VALID["validation"]
DOMAIN["domain / business rules"]
FLOW["workflow / orchestration"]
FMT["formatters"]
TYPES --> VALID --> DOMAIN --> FLOW
DOMAIN --> FMT
end
classDef core fill:#2d5016,stroke:#4a7c23,color:#e8f5e9
class CORE,TYPES,VALID,DOMAIN,FLOW,FMT core












Why Bother?



Testability without ceremony:



Core tests need zero setup - no mocks, no DB, no async. Plain object literals in, assertions out. If a test needs beforeEach or await, the function is in the wrong layer.



Deterministic replay:



Pure functions mean you can log inputs at the shell boundary and reproduce any production bug exactly - no database state, no timing, no environment to reconstruct.



No framework lock-in at the core:



src/core/ has zero runtime dependencies. Switching from Express to Hono, Drizzle to Prisma, or Node to Bun touches only the shell. Business logic is untouched.



Parallel development:



Once core types and signatures are defined, shell and business logic can be built simultaneously. The contract is just data in, data out.



Free documentation:



Pure function signatures are the spec. canTransitionTo(current: TaskStatus, next: TaskStatus): boolean tells you everything - no layer-tracing required.



Safer code review:



Any PR touching only src/core/ cannot introduce a regression caused by I/O, timing, or external state. That's a meaningful trust boundary.



Incremental adoption:



No minimum viable structure. Extract one pure function from a messy handler and grow from there. Unlike DDD or clean architecture, it scales down.









How It Compares









vs. Clean Architecture / Hexagonal Architecture (Ports & Adapters)



Clean Architecture (Robert Martin) and Hexagonal Architecture (Alistair Cockburn) solve the same dependency problem - keep business logic independent of infrastructure - but through abstraction layers: interfaces, ports, adapters, and dependency injection containers.



FCIS gets there through data flow instead. No interfaces. No adapter classes. No DI framework. The core is isolated not by indirection, but because it literally only speaks in plain data types and pure functions.




































Clean / Hexagonal FCIS
ISOLATION MECHANISM INTERFACES + DIPure functions + data
Boilerplate High
Testability Good (with mocks)
Learning curve Steep
Best fit Large teams, complex domains
Small-to-medium codebases



SUGGESTION



Clean Architecture: is powerful

FCIS is cheaper



Pick the one that matches your actual complexity.










vs. Domain-Driven Design (DDD)



DDD (Eric Evans, Domain-Driven Design, 2003) is a design philosophy - ubiquitous language, bounded contexts, aggregates, domain events. FCIS is an architectural pattern. They aren't competitors.






Key differences:




  • DDD encourages rich domain models, - objects that encapsulate both data and behaviour (Aggregates, Entities, Value Objects with methods). FCIS enforces the opposite: data and behaviour are always separate. A Task in FCIS is a plain type; canTransitionTo is a standalone function.


  • You can apply DDD thinking (bounded contexts, ubiquitous language) to a FCIS codebase. But you cannot use rich OOP domain objects in src/core/ without violating the purity constraint.


  • The classic Service → Repository → Database stack organises code by technical role. Business logic typically lives in a Service class that also coordinates I/O - calling repositories, dispatching events, logging. The layers are present, but the boundary between logic and I/O is blurry.




FCIS makes that boundary a hard rule. The equivalent of a Service is split in two: pure logic goes to src/core/, orchestration goes to src/shell/. There's no "service that also does I/O" - that's the entire violation FCIS exists to prevent.









vs. Functional Programming (pure FP)



Languages like Haskell enforce purity at the type system level - impure code must be declared as such (e.g. IO monad). FCIS is a convention-based approximation of that discipline in TypeScript. There's no compiler enforcement of the core/shell boundary - it relies on discipline and linting.



The tradeoff is pragmatism: you get most of the reasoning and testability benefits of pure FP without leaving the TypeScript ecosystem or retraining your team.




Gary Bernhardt's Boundaries talk (2012) is the canonical introduction to this idea. His framing: push values to the edges, keep the centre pure.










The Core (src/core/)





  1. ALLOWED:




    • domain types

    • validation

    • business rules

    • data transformations

    • pure formatters




  2. FORBIDDEN:




    • async/await

    • fetch

    • fs.*

    • console.log

    • process.env

    • new Date()

    • DB calls.




  3. EXAMPLES:


    // ✅ Pure validation - returns Result, never throws
    export const validateTitle = (title: string) => {
    if (!title.trim()) return fail(Errors.validation('title', 'Cannot be empty'))
    return ok(title.trim())
    }

    // ✅ Pure business rule - receives `now` as param, never calls new Date() internally
    export const isOverdue = (task: Task, now: Date) => {
    return !!task.dueAt && task.status !== 'done' && task.dueAt < now
    }

    // ✅ Pure workflow - all data arrives as params, returns computed result
    export const createTask = (project: Project, input: CreateTaskInput, now: Date) => {
    const title = validateTitle(input.title)
    if (!title.ok) return fail(title.error)
    return ok({ id: randomUUID(), ...input, createdAt: now, updatedAt: now })
    }











The Shell (src/shell/)




💡 Every handler follows the same 5 steps - no exceptions:





  1. PARSE → extract input from args/env/stdin

  2. FETCH → read required data from DB/filesystem

  3. CALL → pass data to core, inspect Result

  4. ACT → persist what core returned

  5. OUTPUT → print to stdout/stderr




💡 If you're making a business decision in step 4,

move it to step 3.





// ✅ Thin handler - all decisions happen in core
export const taskDoneCommand = async (taskId: string) => {
// 1. PARSE
if (!taskId) { printError('ID required'); process.exit(1) }

// 2. FETCH
const task = await findTaskById(db, taskId)
if (!task) { printError('Not found'); process.exit(1) }

// 3. CALL CORE
const result = transitionTask(task, { taskId, toStatus: 'done' }, new Date())
if (!result.ok) { printError(result.error.message); process.exit(1) }

// 4. ACT
await updateTask(db, result.value.updatedTask)

// 5. OUTPUT
printSuccess('Task marked as done.')
}









ERROR HANDLING




💡 Use Result - never throw for expected failures.





type Result<T, E = AppError> = { ok: true; value: T } | { ok: false; error: E }

const ok = <T>(value: T): Result<T, never> => ({ ok: true, value })
const fail = <E>(error: E): Result<never, E> => ({ ok: false, error })






Expected failures are values. Thrown exceptions are for truly unexpected crashes only.






TESTING




💡 No setup. No mocks. No async.





// ✅ No setup. No mocks. No async.
it('rejects empty title', () => {
const result = validateTitle('')
expect(result.ok).toBe(false)
})

it('blocks invalid transitions', () => {
expect(canTransitionTo('done', 'todo')).toBe(false)
})

it('returns a new task without mutating the original', () => {
const task = makeTask({ title: 'Original' })
const updated = applyTaskUpdate(task, { title: 'Updated' }, new Date('2024-06-01'))
expect(updated.title).toBe('Updated')
expect(task.title).toBe('Original')
})












Bu, when NOT TO USE IT?




💡 NOTE:




  • When the business logic is the I/O - e.g. a file watcher, a sync tool. The core/shell distinction collapses when there's nothing to separate.

  • When the team is deeply invested in OOP/DDD and the retraining cost outweighs the benefit.










Pre-Commit Checklist




  • [ ] No async/await in src/core/

  • [ ] No imports from src/shell/ in src/core/

  • [ ] No console.log, process.env, fs.*, fetch in src/core/

  • [ ] new Date() only in shell, passed as param into core

  • [ ] All expected failures return Result, nothing throws

  • [ ] Shell handlers follow parse → fetch → call → act → output

  • [ ] Core tests: no mocks, no DB, no async









REFERENCES



1. Sofort-Triage & Abwehrmaßnahmen

SOC Incident Playbook: Remote Code Execution (RCE) Defense
Syntax validiert (0 Fehler)
title: Detect Exploitation - FCIS — Functional Core, Imperative Shell
id: 9fc9cf8b-388b-4af2-90f9-7816ee96e677
status: experimental
description: Automatisch generierte SIEM-Erkennungsregel basierend auf CTI Intelligence
references:
  - https://tsecurity.de/
author: iShareStuff CTI Automated Detection Engine
date: 2026-09-27
logsource:
  category: network_connection
  product: any
detection:
  selection:
      CommandLine|contains:
        - 'exploit'
  condition: selection
falsepositives:
  - Legitime administrative Zugriffe oder Penetrationstests
level: high
tags:
  - attack.initial_access
Syntax validiert (0 Fehler)
rule CTI_Threat_Indicator {
    meta:
        author = "iShareStuff CTI Automated Detection Engine"
        date = "2026-09-27"
        description = "YARA Signature for "
    strings:
        $str = "FCIS — Functional Core, Impera" ascii wide
    condition:
        any of them
}
Syntax validiert (0 Fehler)
index=security sourcetype IN ("cisco:asa", "pan:traffic", "zeek_conn", "suricata", "WinEventLog:Security")
("FCIS  Functional Core Imperative Shell")
| stats count earliest(_time) as first_seen latest(_time) as last_seen by src_ip, dest_ip, dest_host, signature
| eval first_seen=strftime(first_seen, "%Y-%m-%d %H:%M:%S"), last_seen=strftime(last_seen, "%Y-%m-%d %H:%M:%S")
| sort - count
Syntax validiert (0 Fehler)
message: "*FCIS  Functional Core Imperative Shell*"
Syntax validiert (0 Fehler)
CommonSecurityLog
| where Message has "FCIS  Functional Core Imperative Shell"
| summarize EventCount = count(), FirstSeen = min(TimeGenerated), LastSeen = max(TimeGenerated) by SourceIP, DestinationIP, DestinationPort, Activity
| extend DetectionRule = "iShareStuff-CTI-Compiled"
| sort by EventCount desc

2. Cyber Threat Intelligence & Forensik

🎯
MITRE ATT&CK Matrix Navigator 14 Taktiken
Reconnaissance
-
Resource Development
-
Initial Access
Execution
Persistence
-
Privilege Escalation
Defense Evasion
Credential Access
-
Discovery
-
Lateral Movement
-
Collection
-
Command and Control
Exfiltration
-
Impact
tsecurity.de Cognitive Threat RAG
Fokus-Vektor:

Analyse für identifizierte Bedrohung auf Basis von Live-CTI (ENISA EUVD): CVSS 0.0 · EPSS 0.0% · CISA KEV: nein. Handlungsableitung aus den verlinkten Hersteller-Quellen.

🛡️ Angriffsfläche & Exposure

Netzwerk/Remote-Zugriff ohne Vorauthentifizierung möglich.

⚡ Empfohlene Sofortmaßnahmen
  • 1. Perimeter-Inspektion: Relevante Portfreigaben und exponierte Endpunkte unverzüglich scannen.
  • 2. Patch-Applikation: Hersteller-Hotfix einspielen oder betroffene Daemons in isolierte DMZ-Segmente überführen.
  • 3. Telemetrie & EDR-Alerts: Prozessaufrufe und Child-Processes auf anomale Shell-Spawns überwachen.
Ähnliche Beiträge
🔍 Verwandte News

Auch interessante Nachrichten FCIS — Functional Core, Imperative Shell

Thematisch verwandte Begriffe: FCIS, Functional, Core, Imperative · 6 Treffer

Laden...

Videos werden geladen ...

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...

💬 Kommentare werden geladen…
Zum Aktualisieren ziehen
ZERO-DAY CVE-2025-71424 | Contrast, Edgeless Systems' runtime for confidential containers on Kuber…
Advisory →
tsecurity.de Icon
Offline-Lesen, Eilmeldungen & 0ms Ladezeit

Installiere tsecurity.de direkt auf deinen Home-Bildschirm für das ultimative Vollbild-Magazinerlebnis ohne Browser-Leisten.

Nächster Beitrag