ServicesCrelio AppArchitectureApp Modules

core

Shared infrastructure, base models, utilities, and service clients

Core App Architecture

Domain Responsibility

What This App Owns

  • BaseModel foundation with lifecycle hooks
  • Utility clients for external services (ES, S3, Redis, Slack, Pusher, DocumentDB)
  • Middleware for authentication, sessions, and request handling
  • Decorators for common view patterns
  • Shared utilities (date, encryption, translation, PDF, image processing)

What It Depends On

  • Django framework
  • External service SDKs (boto3, elasticsearch, redis-py)

What Should NOT Be Added Here

  • Domain-specific business logic (belongs in domain apps)
  • Feature-specific models (belongs in domain apps)
  • API endpoints for specific features

Model-Centric Design (Fat Models)

Key Models

ModelResponsibilityKey FieldsRelations
BaseModelAbstract base with lifecycle hookscache_type, allow_individual_instance_caching, webhook_enabledInherited by all domain models
ActivityLogBaseMixin for activity loggingcategory_id_mapperMixed into BaseModel
DocumentDBConnectionDocumentDB connection configaccount_id, connection_urlNone

Business Logic in Models

BaseModel (core/models/base.py)

MethodPurpose
validate(*args, **kwargs)Override for input validation
before_save(*args, **kwargs)Override for pre-save logic
after_save(*args, **kwargs)Override for post-save side effects
save(*args, **kwargs)Orchestrates validation → before_save → DB save → after_save
set_values(values: dict)Bulk set attributes from dictionary
validate_mandatory_fields()Check required fields are present
get(ExceptionToRaise, **filters)Fetch single instance or raise
get_cached_instance(instance_id, ...)Fetch from Redis cache
notify_on_slack(channel, message, ...)Send Slack notification

ActivityLogBase (core/models/activity_log_base.py)

MethodPurpose
add_activity_log(action, message, session, ...)Log activity to Elasticsearch
get_activities(instance_id, start_date, end_date, ...)Retrieve activity logs
prepare_activity_log_payload(action, session)Override to customize log payload

Why Fat Model Style Here

The core app provides infrastructure mixins, not domain logic. Models in domain apps inherit from BaseModel to get:

  1. Consistent lifecycle hooks
  2. Activity logging capability
  3. Caching utilities
  4. Slack notifications

Invariants & Data Rules

BaseModel Constraints

  • save() always calls validate()before_save() → super().save() → after_save()
  • If validate() raises ValidationError, save is aborted
  • after_save() runs INSIDE the transaction (careful with external calls)

Cache Keys

  • Format: {ModelName}_CentreId{lab_id}_List or custom cache_key
  • TTL: default_cache_ttl = 7 * 24 * 60 * 60 (7 days)

Data Access Patterns

Cache Access

# Redis hash-based caching
instance = cache.hget(cls.cache_key, instance_id)
if not instance:
    instance = cls.get(pk=instance_id)
    cache.hset(cls.cache_key, instance_id, json.dumps(serialized))

Client Factory Pattern

# core/utils/clients.py
client = get_client("es")       # ElasticSearchClient
client = get_client("s3")       # S3Client
client = get_client("slack")    # SlackClient
client = get_client("fusion")   # FusionClient
client = get_client("docdb", account_id=123)  # DocumentDBClient

API Layer

The core app exposes minimal endpoints:

EndpointViewPurpose
/core/pdf/pdf_view.pyPDF generation utilities
/core/view/view.pyHealth checks

GenericView Base Class

# core/view.py
class GenericView(View):
    """Base view with session utilities"""
    
    def get_lab_id_from_session(self, session):
        """Extract lab_id from session"""
        return session.get("labId") or session.get("docLabId")
    
    def get_child_lab_ids(self, parent_lab_id):
        """Get child labs for multi-tenant queries"""
        return LabRelations.get_child_labs(parent_lab_id)

Integrations

External Services

ServiceClient LocationPurpose
Elasticsearchcore/utils/elastic_search/Activity logs, search
Redis Clustercore/cache.pyCaching, sessions
AWS S3core/utils/aws/client.pyFile storage
Slackcore/utils/slack.pyNotifications
Pushercore/utils/pusher/Real-time updates
DocumentDBcore/utils/documentdb/Integration logs
Fusion Workercore/utils/fusion/Async job queue

Service Client Example

# core/utils/clients.py
def get_client(service: str, account_id=None, ...):
    if service == "es":
        return ElasticSearchClient(config=settings.ES_CONFIG)
    elif service == "s3":
        return S3Client()
    elif service == "slack":
        return SlackClient()
    # ... more clients

Side Effects & Hidden Coupling

No Django Signals

The core app does NOT use signals.py. All side effects are explicit in model methods.

Coupling Points

FromToType
core/models/base.pycore/utils/clients.pyImport for Slack
core/models/activity_log_base.pycore/utils/activity_log/Activity logging
core/view.pyadmin.account.proxies.lab_relationLab hierarchy

Performance & Scaling Notes

Hot Paths

  • cache.hget() / cache.hset() - Redis operations per request
  • get_client("es") - Client instantiation (consider pooling)

Caching Strategy

  • Model instances cached in Redis hashes by (model_key, instance_id)
  • TTL is 7 days by default
  • Manual invalidation via reset_cache_instance(instance_id)

Safe Extension Guide

Adding a New Utility Client

  1. Create client in core/utils/{service_name}/client.py
  2. Register in core/utils/clients.py:
    elif service == "new_service":
        return NewServiceClient(**args)
  3. Add settings in config/settings/

Adding a New Mixin

  1. Create mixin in core/models/{mixin_name}.py
  2. Add to BaseModel's parent classes if universally needed
  3. Or inherit in specific domain models

Patterns to Follow

  • Keep utilities stateless
  • Use dependency injection via get_client()
  • Log errors to Sentry

Patterns to Avoid

  • Domain logic in core/
  • Direct database queries without going through models
  • Synchronous external calls in hot paths

File Map

FilePurpose
core/models/init.pyModel exports
core/models/base.pyBaseModel (303 lines)
core/models/activity_log_base.pyActivityLogBase (166 lines)
core/models/document_db_connection.pyDocumentDB config
core/cache.pyRedis cluster cache (845 lines)
core/view.pyGenericView base (67 lines)
core/utils/clients.pyClient factory (55 lines)
core/utils/elastic_search/ES client (73 files)
core/utils/documentdb/DocDB client
core/utils/fusion/Fusion worker client
core/utils/aws/AWS clients (S3, ELB, ACM)
core/middlewares/Request/auth middleware
core/decorators.pyView decorators

On this page