
# Architecture Principles

This document outlines the fundamental architectural principles and design philosophy of the VIPR Core framework. It provides the "why" behind architectural decisions and explains the conceptual foundation without detailed implementation specifics.

## 1. Workflow Concept

Workflows are the core building blocks of the VIPR Core architecture. They define how meaningful results are generated from given input data.

- **Definition**: A workflow consists of a model class and a standard configuration
- **Process**: The processes are defined by model classes and are largely "fixed"
- **Extensibility**: For new requirements, there are three options:
  1. Extend the model class (only for generic, widely useful changes)
  2. Inherit from/re-implement the model class (while maintaining common callback endpoints) in plugins
  3. Develop plugins that interact with the workflow through callbacks
  
- **Design Consideration**: Hooks and filters are suitable for simple integrations. For deeper interventions in data processing, inheriting from the model class is preferable to plugin solutions. This provides better control, type safety, and maintains architectural integrity.

## 2. Component Types and Their Responsibilities

### 2.1 Handlers

- **Purpose**: Create new objects or load resources
- **Example**: ModelLoader, DataLoader, various resource providers
- **Usage Method**: `app.handler.get('handler_type', 'instance_name', setup=True)`
  - where `handler_type` refers to the interface name
  - and `instance_name` refers to the specific implementation
- **Characteristic**: Concrete implementations of interface classes
- **Note**: In the Cement framework, an interface defines functionality, and a handler implements that functionality

### 2.2 Filters

- **Purpose**: Transform data of the same type
- **Basic principle**: A filter must return exactly the same data type that it receives as input
- **Advantage**: Multiple filters can be chained together
- **Registration**: `app.filter.register(FilterType.X.value, self._x_filter)`
- **Invocation**: `result = app.filter.run(FilterType.X.value, input_data)`
- **Characteristic**: Transforms data without changing the type
- **Constraints**: The framework defines clear rules for filters to ensure modular and predictable interactions between plugins

### 2.3 Hooks

- **Purpose**: Read-only access to data without execution of side effects
- **Characteristic**: Hooks are read-only with respect to the passed data
- **Registration**: `app.hook.register(HookType.X.value, self.on_x)`
- **Invocation**: `self._run_hooks(HookType.X.value, data)`
- **Characteristic**: React to events in the data flow

## 3. Plugin Architecture

Plugins serve to extend the functionality of the core model or other plugins.

### Plugin Principles

- **Independence**: Plugins should be decoupled such that the core does not depend on a plugin
- **State Management**: Plugins maintain their own state, do not modify the core state
- **Callback Usage**: Plugins use defined callback endpoints (hooks, filters)
- **Extension Mechanisms**: Plugins can use existing hooks and filters as well as define their own to provide extension possibilities to other plugins.

## 4. Data Flow Concept

The data flow in VIPR follows clear principles:

- **Initiation**: Handlers create or load resources (e.g., data through DataLoaders, models through ModelLoaders)
- **Processing**: Filters transform the data step by step, maintaining the data type
- **Reaction**: Hooks are triggered at various points and can react to the data
- **Encapsulation**: Each component maintains its own internal state

A typical workflow might look like this:

1. Data is loaded by a DataLoader handler
2. Model is loaded by a ModelLoader handler
3. Input data is normalized by filters
4. Preprocessing of data by additional filters
5. Prediction based on the preprocessed data
6. Post-processing of results
7. Hooks react to various events throughout the process

## 5. Robustness and Type Safety

VIPR employs several mechanisms to ensure robustness and type safety:

### 5.1 Type Checking Mechanism

The framework uses a comprehensive type checking approach, i.e. [beartype](https://github.com/beartype/beartype). Types are checked by calling `beartype_this_package()`, which does the following:

- It automatically decorates all functions and classes in the package with the `@beartype.beartype()` decorator
- It adds runtime type checks for PEP 526-compliant annotated variable assignments
- It limits type checking to the current package in which it is called
- It is typically called as the first statement in a package's `__init__` module to enable type checking for the entire package

### 5.2 Hook and Filter Requirements

Plugins can explicitly declare their dependency on hooks and filters via the `vipr.ext.hooks_filters_requirements` extension:

- **Required Dependencies**: `@required()` decorator marks hooks/filters essential for the plugin to function
- **Optional Extensions**: `@optional()` decorator marks hooks/filters that enhance but aren't critical
- **Enforcement**: The framework validates requirements at startup, preventing plugin failures due to missing dependencies
- **Scope of Validation**: It's important to note that the framework only checks if hooks/filters are defined, not whether they are actually executed during runtime

### 5.3 Integration Principles

These mechanisms work together to ensure the following:

- **Type Safety**: All data flowing through hooks and filters is checked against expected types
- **Plugin Compatibility**: Required hooks and filters are validated before execution
- **Explicit Contracts**: Clear specification of what each component needs and provides
- **Self-Documentation**: Type annotations and requirement decorators serve as living documentation
