Skip to content

Chat — Services

Overview

The services layer is the workflow core of the chat app.

It is responsible for:

  • creating conversations
  • listing conversations with unread metadata
  • validating participant access
  • creating, editing, and deleting messages
  • uploading and attaching files
  • managing conversation participants
  • updating read state
  • exposing notification integration points

The services layer should hold the business rules of chat.

It sits between:

  • views and serializers
  • models
  • websocket consumers
  • tasks
  • notifications

Service structure

The chat services are split into focused modules:

  • services/conversations.py
  • services/messages.py
  • services/attachments.py
  • services/participants.py
  • services/read_state.py
  • services/notifications.py

This split keeps the app easier to reason about and makes both REST and websocket code reusable.


services/conversations.py

This module owns conversation-level workflows.

Main responsibilities

  • list conversations for internal org users
  • list conversations for customer org users
  • create conversations
  • validate participant access to conversations
  • calculate unread counts
  • fetch last message summaries

Typical functions

  • participant_exists(...)
  • get_conversation_for_participant(...)
  • conversation_unread_count(...)
  • get_last_message_for_conversation(...)
  • list_conversations_for_internal_org(...)
  • list_conversations_for_customer_org(...)
  • create_conversation(...)

Why it matters

Conversation listing for frontend use is more than a plain queryset.

It often needs:

  • unread count
  • participant count
  • latest message preview

This module builds that read model.


services/messages.py

This module owns message-level workflows.

Main responsibilities

  • validate participant access before reading or writing messages
  • list paginated message history
  • create messages
  • link uploaded attachments to messages
  • edit messages
  • soft-delete messages

Typical functions

  • ensure_participant(...)
  • get_message_for_participant(...)
  • list_messages(...)
  • create_message(...)
  • edit_message(...)
  • delete_message(...)

Why it matters

This module becomes the shared write boundary for:

  • REST message operations
  • websocket message creation

That keeps message behavior consistent across transport layers.


services/attachments.py

This module owns attachment upload and linking behavior.

Main responsibilities

  • validate upload access
  • validate upload size and content type
  • create attachment rows
  • find unclaimed attachment IDs
  • attach uploaded files to a message

Typical functions

  • ensure_can_access_conversation(...)
  • validate_attachment_upload(...)
  • upload_attachment(...)
  • list_unclaimed_attachment_ids_for_user(...)
  • attach_attachments_to_message(...)

Why it matters

The app supports an upload-first workflow:

  1. upload file
  2. get attachment ID
  3. send message with attachment IDs
  4. link those attachments to the new message

This module owns that lifecycle.


services/participants.py

This module owns participant membership behavior.

Main responsibilities

  • ensure the acting user is an active participant
  • list active participants
  • add or reactivate participants
  • remove participants by deactivating them
  • validate that added users belong to an allowed org for the conversation

Typical functions

  • ensure_active_participant(...)
  • list_participants(...)
  • add_participant(...)
  • remove_participant(...)

Why it matters

Participant state controls:

  • access to conversations
  • access to message history
  • access to attachment upload
  • websocket eligibility

It is one of the core security boundaries of the app.


services/read_state.py

This module owns conversation read tracking.

Main responsibilities

  • mark a conversation as read for a participant
  • update last_read_at

Typical functions

  • mark_conversation_read(...)

Why it matters

The current unread system is simple and efficient:

  • each participant stores one read cursor
  • unread count is derived by comparing message timestamps against that cursor

This module keeps that logic explicit.


services/notifications.py

This module is the integration boundary for notifying participants about new messages.

Main responsibilities

  • define how chat events trigger notifications
  • isolate notification integration from message persistence code

Typical functions

  • notify_participants_new_message(...)

Why it matters

Chat notifications are related to messaging, but should not be hardcoded in views or consumers.

Keeping them behind a service boundary makes it easier to:

  • align with the notifications app
  • change channels later
  • keep message creation logic transport-agnostic

Error model

Each service area uses domain-specific exceptions.

Examples include:

  • permission errors
  • validation errors
  • conversation errors
  • message errors
  • attachment errors
  • participant errors
  • read state errors

Views translate these into HTTP responses such as:

  • 400
  • 403
  • 404
  • 409

This keeps services independent from HTTP.


Core workflow flows

Conversation creation flow

Creating a conversation requires more than just inserting a row.

Main steps

  1. validate the internal org is linked to the target customer org
  2. create the conversation
  3. add the creator as a participant
  4. return the created conversation

Mermaid flow

flowchart TD
    A[create_conversation] --> B{Customer org linked?}
    B -->|no| C[Raise permission error]
    B -->|yes| D[Create Conversation]
    D --> E[Create Participant for actor]
    E --> F[Return conversation]

Conversation list flow

Conversation list endpoints need frontend-oriented metadata.

Main steps 1. fetch conversations for the relevant org 2. compute unread count for current user 3. compute participant count 4. fetch latest message 5. return enriched conversation objects

Mermaid flow

flowchart TD
    A[List conversations] --> B[Fetch conversations]
    B --> C[Compute unread_count]
    C --> D[Compute participant_count]
    D --> E[Fetch last_message]
    E --> F[Return enriched list]

Message creation flow

Message creation is one of the most important workflows in the app.

Main steps

  1. validate active participant access
  2. create the message
  3. attach uploaded files if attachment IDs are provided
  4. optionally notify other participants
  5. return the message and attached IDs

Mermaid flow

flowchart TD
    A[create_message] --> B{Active participant?}
    B -->|no| C[Raise permission error]
    B -->|yes| D[Create Message]
    D --> E{Attachment IDs provided?}
    E -->|yes| F[Attach unclaimed uploads]
    E -->|no| G[Skip attachment linking]
    F --> H[Optionally notify participants]
    G --> H
    H --> I[Return message + attachment ids]

Message edit flow

Main steps

1.  load message visible to participant
2.  ensure sender is the actor
3.  update body
4.  set edited_at
5.  return updated message

Mermaid flow

flowchart TD
    A[edit_message] --> B[Load message]
    B --> C{Participant has access?}
    C -->|no| D[Raise permission error]
    C -->|yes| E{Actor is sender?}
    E -->|no| F[Raise permission error]
    E -->|yes| G[Update body]
    G --> H[Set edited_at]
    H --> I[Return updated message]

Message delete flow

Delete is implemented as soft delete.

Main steps

1.  load message visible to participant
2.  ensure sender is the actor
3.  set deleted_at
4.  return updated message

Mermaid flow


flowchart TD
    A[delete_message] --> B[Load message]
    B --> C{Participant has access?}
    C -->|no| D[Raise permission error]
    C -->|yes| E{Actor is sender?}
    E -->|no| F[Raise permission error]
    E -->|yes| G[Set deleted_at]
    G --> H[Return updated message]

Attachment upload flow

Main steps

1.  ensure user can access the conversation
2.  validate upload size
3.  validate content type
4.  create attachment row
5.  return attachment metadata

Mermaid flow

flowchart TD
    A[upload_attachment] --> B{Can access conversation?}
    B -->|no| C[Raise permission error]
    B -->|yes| D[Validate upload]
    D -->|invalid| E[Raise validation error]
    D -->|valid| F[Create MessageAttachment]
    F --> G[Return attachment]

Attachment linking flow

Main steps

1.  receive attachment IDs during message creation
2.  find unclaimed attachments uploaded by this user in this conversation
3.  link them to the new message
4.  return linked attachment IDs

Mermaid flow


flowchart TD
    A[attach_attachments_to_message] --> B[Filter by conversation]
    B --> C[Filter by uploaded_by]
    C --> D[Filter by unclaimed uploads]
    D --> E[Update message_id]
    E --> F[Return linked ids]

Participant membership flow

Add participant

  1. load conversation
  2. ensure actor is active participant
  3. ensure target user belongs to allowed org side
  4. create or reactivate participant row
  5. return participant

Remove participant

  1. load conversation
  2. ensure actor is active participant
  3. deactivate target participant row

Mermaid flow

flowchart TD
    A[add_participant] --> B[Load conversation]
    B --> C{Actor is active participant?}
    C -->|no| D[Raise permission error]
    C -->|yes| E{Target user belongs to internal or customer org?}
    E -->|no| F[Raise permission error]
    E -->|yes| G[Create or reactivate Participant]
    G --> H[Return participant]

Read-state flow

Main steps

  1. find active participant row
  2. update last_read_at
  3. return updated participant

Mermaid flow

flowchart TD
    A[mark_conversation_read] --> B[Find active participant]
    B -->|not found| C[Raise permission error]
    B -->|found| D[Set last_read_at]
    D --> E[Save participant]
    E --> F[Return participant]

Shared use between REST and websocket

One of the main goals of the service split is reusability.

REST

Views call services to: • list data • create records • edit/delete records • manage read state and participants

Websocket

Consumers should call the same services to: • validate participant access • create messages • link attachments

This avoids having one workflow for REST and another for realtime.


Data flow


flowchart TD
    A[View or Consumer] --> B[Service]
    B --> C[Models]
    B --> D[Notifications boundary]
    C --> E[Updated state]
    E --> F[Serializer or websocket payload]

Responsibilities by layer

Services layer owns

  • conversation creation rules
  • participant membership checks
  • unread count logic
  • message persistence workflows
  • attachment linking rules
  • sender-only edit/delete rules
  • read-state updates
  • notification integration boundary

Views layer owns

  • HTTP request handling
  • serializer invocation
  • status code mapping

Consumers layer owns

  • websocket connect/disconnect behavior
  • websocket payload parsing
  • group broadcasting

Serializers layer owns

  • input validation
  • response shaping

Models layer owns

  • persistence
  • relational structure

What services do not do

Services do not:

  • parse raw websocket JSON
  • return HTTP responses directly
  • define serializer fields
  • emit websocket group events directly
  • define URL routing
  • own file storage settings

Those concerns belong to:

  • consumers
  • views
  • serializers
  • routing
  • storage config

Design principles

Thin views and consumers

Views and websocket consumers should orchestrate, not implement business rules.

Shared workflows across transport layers

REST and websocket paths should reuse the same message and attachment logic.

Participant-first authorization

Most chat actions depend on participant status, so access validation is centralized in services.

Upload-first attachment lifecycle

The app supports mobile- and frontend-friendly attachment workflows by separating upload from message creation.

Soft-delete over destructive delete

Messages are hidden by timestamp rather than removed immediately.


Best practices

  • call services for all conversation and message workflows
  • keep authorization checks centralized in services
  • reuse message creation logic from both REST and websocket code
  • treat attachments as conversation-scoped before message linking
  • keep notification integration behind a service boundary
  • keep unread logic based on last_read_at, not duplicated in views

Future extensions

The service layer is well positioned to support:

  • message reactions
  • conversation archive/mute
  • topic-specific conversation creation helpers
  • read receipt expansion
  • system messages
  • typing persistence or throttling
  • attachment virus scanning hooks
  • conversation close/reopen workflows
  • notification preferences for chat messages

Summary

The chat services layer is the workflow engine of the app.

It provides the business behavior for:

  • conversations
  • messages
  • attachments
  • participants
  • read state
  • notification hooks

This makes the chat app:

  • reusable across REST and websocket paths
  • easier to test
  • easier to document
  • much more frontend-ready