Skip to content

fraktalio/Information-Systems

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

11 Commits
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

Information Systems in Kotlin

fmodel

Information systems as composable algebraic structures. Unifies state-stored (πŸ›οΈ store current state) and event-sourced (πŸŒ€ store past events) architectures under a single model.

The Journey: From General to Specific

The library is structured as a progression from the most general abstraction to practical implementations. Each file builds upon the previous one through specialization:

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ IGeneralSystem<Command, InState, OutState, InEvent, OutEvent>  β”‚
β”‚ (5 type parameters - most general)                              β”‚
β”‚ β€’ decide: (Command, InState) -> Sequence<OutEvent>              β”‚
β”‚ β€’ evolve: (InState, InEvent) -> OutState                        β”‚
β”‚ β€’ initialState: () -> OutState                                  β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                             β”‚
                             β”‚ InState == OutState == State
                             β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ IDynamicSystem<Command, State, InEvent, OutEvent>               β”‚
β”‚ extends IGeneralSystem<Command, State, State, InEvent, OutEvent>β”‚
β”‚ (4 type parameters)                                              β”‚
β”‚ β€’ State types unified                                            β”‚
β”‚ β€’ Can convert to EventSourcedSystem                              β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                             β”‚
                             β”‚ InEvent == OutEvent == Event
                             β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ ISystem<Command, State, Event>                                  β”‚
β”‚ extends IDynamicSystem<Command, State, Event, Event>            β”‚
β”‚ (3 type parameters - most common)                               β”‚
β”‚ β€’ Event types unified                                            β”‚
β”‚ β€’ Can convert to StateStoredSystem                               β”‚
β”‚ β€’ Can convert to EventSourcedSystem (limited)                    β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                             β”‚
                             β”‚ Add persistence + metadata
                             β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ IStateRepository / IEventRepository                             β”‚
β”‚ (Infrastructure layer - metadata support)                        β”‚
β”‚ β€’ Metadata never leaks into domain                               β”‚
β”‚ β€’ Transactional semantics (fetch β†’ compute β†’ save)               β”‚
β”‚ β€’ handle() extension functions                                   β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

1. GeneralSystem (5 type parameters)

src/1_GeneralSystem.kt β€” The foundation

The most general form that captures all possible information systems:

interface IGeneralSystem<Command, InState, OutState, InEvent, OutEvent> {
    val decide: (Command, InState) -> Sequence<OutEvent>
    val evolve: (InState, InEvent) -> OutState
    val initialState: () -> OutState
}

This abstraction allows:

  • Different input/output state types (InState β‰  OutState)
  • Different input/output event types (InEvent β‰  OutEvent)
  • Full functorial and profunctor transformations
  • Monoidal composition via combine

2. DynamicSystem (4 type parameters)

src/2_DynamicSystem.kt β€” First specialization

Constrains state types to be equal: InState == OutState == State

interface IDynamicSystem<Command, State, InEvent, OutEvent> :
    IGeneralSystem<Command, State, State, InEvent, OutEvent>

This enables:

  • Event-sourced systems (events can differ: InEvent β‰  OutEvent)
  • State reconstruction from event sequences
  • Conversion to EventSourcedSystem type alias

3. System (3 type parameters)

src/3_System.kt β€” Second specialization

Further constrains event types to be equal: InEvent == OutEvent == Event

interface ISystem<Command, State, Event> :
    IDynamicSystem<Command, State, Event, Event>

This is the most common form, enabling:

  • Both state-stored and event-sourced architectures
  • Conversion to StateStoredSystem type alias
  • Conversion to EventSourcedSystem type alias (limited to InEvent == OutEvent)
  • Full bidirectional system representation

Note: The constraint InEvent == OutEvent means this system can only handle event-sourced scenarios where the events you read are the same type as the events you produce. For more flexible event transformations (InEvent β‰  OutEvent), use DynamicSystem instead.

4. StatefulSystem (Persistence layer)

src/4_StatefulSystem.kt β€” Practical implementation

Adds repository interfaces for persistence with metadata support:

interface IStateRepository<Command, CommandMetadata, State, StateMetadata> {
    suspend fun fetchState(command: Pair<Command, CommandMetadata>): Pair<State, StateMetadata>?
    suspend fun save(state: Pair<State, CommandMetadata>): Pair<State, StateMetadata>
    suspend fun process(command: Pair<Command, CommandMetadata>, system: StateStoredSystem<Command, State>): Pair<State, StateMetadata>
}

interface IEventRepository<Command, CommandMetadata, InEvent, InEventMetadata, OutEvent, OutEventMetadata> {
    suspend fun fetchEvents(command: Pair<Command, CommandMetadata>): Sequence<Pair<InEvent, InEventMetadata>>
    suspend fun save(events: Sequence<OutEvent>, commandMetadata: CommandMetadata): Sequence<Pair<OutEvent, OutEventMetadata>>
    suspend fun process(command: Pair<Command, CommandMetadata>, system: EventSourcedSystem<Command, InEvent, OutEvent>): Sequence<Pair<OutEvent, OutEventMetadata>>
}

This provides:

  • Separation of domain logic from persistence
  • Transactional semantics (fetch β†’ compute β†’ save)
  • Convenient handle extension functions
  • Metadata support at infrastructure/application level

Metadata: Infrastructure Concern, Not Domain Concern

A key design principle: metadata exists only at the infrastructure/application boundaries and never leaks into the domain layer.

Domain systems (IGeneralSystem, IDynamicSystem, ISystem) remain pure and focused on business logic:

decide: (Command, State) -> Sequence<Event>  // No metadata here

Metadata is introduced at the repository level for infrastructure concerns:

  • Versioning and optimistic locking
  • Audit trails (who, when, from where)
  • Correlation IDs for distributed tracing
  • Event sequence numbers and timestamps
  • Tenant/user context for multi-tenancy

The process method orchestrates the flow:

  1. Fetch data with metadata from storage
  2. Extract pure domain data and pass to domain system (metadata-free)
  3. Domain system produces new data (pure business logic)
  4. Attach metadata and persist

This separation keeps domain logic testable, composable, and free from infrastructure concerns.

Composition Through Multiple Type Constraints

The handle extension functions demonstrate elegant composition at the type level:

suspend fun <SYSTEM, Command, State, Event, CommandMetadata, EventMetadata> 
    SYSTEM.handle(command: Pair<Command, CommandMetadata>): Sequence<Pair<Event, EventMetadata>>
        where SYSTEM : IEventRepository<Command, CommandMetadata, Event, EventMetadata, Event, EventMetadata>,
              SYSTEM : ISystem<Command, State, Event> =
    process(command, inEventSourcedSystem())

This composition pattern:

  • Type-level composition: Any type implementing both IEventRepository and ISystem automatically gains handle capability
  • Separation of concerns: Repository provides persistence, System provides domain logic
  • No inheritance required: Composition through interfaces, not class hierarchies
  • Metadata isolation: Domain system extracted via inEventSourcedSystem() remains metadata-free

Example implementation:

class OrderSystem : 
    ISystem<OrderCommand, OrderState, OrderEvent>,
    IEventRepository<OrderCommand, UserContext, OrderEvent, EventMetadata, OrderEvent, EventMetadata> {
    
    // Domain logic (pure)
    override val decide = { command: OrderCommand, state: OrderState -> /* ... */ }
    override val evolve = { state: OrderState, event: OrderEvent -> /* ... */ }
    override val initialState = { OrderState.empty() }
    
    // Infrastructure (with metadata)
    override suspend fun fetchEvents(command: Pair<OrderCommand, UserContext>) = /* ... */
    override suspend fun save(events: Sequence<OrderEvent>, metadata: UserContext) = /* ... */
}

// Composition happens automatically - handle() is now available
val result = orderSystem.handle(PlaceOrder(...) to UserContext(userId = "123"))

Algebraic View

Concept Mathematical Structure Notes
mapCommand Contravariant Functor Maps input command types
mapEvent Profunctor (dimap) Contravariant in input events, covariant in output
mapState Profunctor (dimap) Contravariant in input state, covariant in output
combine Monoidal Product Combines two systems into a product system
emptySystem Monoidal Identity Represents the "no-op" system (GeneralSystem<Nothing?, Unit, Nothing?>)

Why Systems Thinking Matters in the AI Era

As software engineering continues to raise the level of abstraction through AI, systems thinking becomes even more essential. While AI tools grow increasingly powerful at generating code, the real value lies in understanding how components interact, how decisions ripple through ecosystems, and how to design resilient, adaptable systems.

This library embodies that philosophy: composable algebraic structures that let you reason about system behavior at a higher level. When AI assists in implementation, your focus shifts to architecture, composition, and the invariants that matterβ€”exactly where human insight creates the most value.

Data & Behaviour = Information (Algebra of the Business)

  • Data (examples/api.kt): Commands, Events, State
  • Behaviour (examples/domain.kt): Exhaustive pattern matching on data

πŸ“š Learn More - fmodel.fraktalio.com

domain modeling

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Languages