Skip to main content
Mostafa Effati

About this platform

A working notebook for engineering practice

This is Mostafa Effati’s personal engineering platform: a place to turn hands-on work into clear writing, structured learning, and useful digital products.

Student collaborator

  • Ms Zahra Ehsani

    Former student · platform contributor

    Ms Zahra Ehsani worked on parts of this platform during her studies and brought thoughtful, practical contributions to its development. I'm glad to recognize her work here.

What you will find

  • Long-form articles, books, tutorials, and practical snippets.
  • Courses and guided learning paths built around real engineering work.
  • Case studies, open-source work, experiments, and product releases.

Built deliberately

  • A bilingual Persian and English experience with native RTL support.
  • Theme-aware, keyboard-friendly interfaces with a focused visual language.
  • Content and product changes developed as one versioned system.

The platform is continuously evolving. Some sections are complete products; others are active experiments that improve as the underlying work develops.

Contact Information as an Aggregate

Why contact channels should not live as fields on the User account.
Mostafa Effati3 min readsoftware-architecture

Why I Didn’t Store Contact Information Directly in User Accounts

A small architectural decision that makes identity systems easier to scale and maintain.

A typical schema looks like this:

User
 ├── email
 ├── phone_number
 ├── telegram
 ├── whatsapp
 └── ...

It feels simple — until the User entity accumulates responsibilities that don’t belong to it.

The problem in one diagram

People rarely have a single forever-email or phone. Channels multiply; apps invent new ones.

Thinking in aggregates

Instead of stuffing channels into User, I introduced a Contact aggregate:

User Account
     │
     ├── Email
     ├── Mobile
     ├── Telegram
     └── WhatsApp
  • User → identity within the system
  • Contact → how you reach them
  • Each aggregate owns its rules and lifecycle

What each contact can own

Verification, encryption, and validation differ by channel. That’s easier when contacts are first-class:

Benefits

1. Extensibility

New contact type → no User-model migration for every product change.

2. Separation of responsibilities

Accounts and channels change for different reasons. Independence reduces accidental complexity.

3. Better security controls

Different channels need different verification workflows, encryption policies, and validation rules.

4. More realistic modeling

People change numbers. They add Telegram. They retire an email. The model should allow that without pretending everyone has exactly one of each forever.

How accidental complexity grows

  1. Day one

    One email column. Ship it.

  2. Month three

    Secondary phone. “Just one more field.”

  3. Year one

    Exceptions, nulls, and half-verified channels living on User.

  4. The refactor

    Contact aggregate — what you wished you had on day one.

The point

The goal wasn’t more tables. The goal was clearer boundaries: access vs communication.

Continue with Citizenship as a Relationship.


Canonical version. Also on Medium.

Related documents

Command menu

↑ ↓to move · Enter to select · Esc to close
Navigation
Actions

Press Esc to close · ⌘K to toggle