ЁЯПл The SchoolтА║ЁЯПЫя╕П Software ArchitectureтА║ЁЯЧДя╕П рдзрдбрд╛ 08 тАФ Data ownership: рд╕рд╛рдорд╛рдпрд┐рдХ рдХрдкрд╛рдЯ, рдПрдХ рдЦрд┐рдбрдХреА, рдХреА рдиреЛрдЯрд┐рд╢реАрдЪреА рдкреНрд░рдд
ЁЯЦ╝я╕П See the drawing + lab ЁЯПа Course home ЁЯМ┐ Branch on GitHub тЬПя╕П View source
ЁЯЦ╝я╕П рдЖрдХреГрддреА рдЖрдгрд┐ labThe drawing + lab рдкреВрд░реНрдг рдкрд╛рдирд╛рд╡рд░ рдЙрдШрдбрд╛ тЖЧOpen full page тЖЧ

ЁЯЧДя╕П рдзрдбрд╛ 08 тАФ Data ownership: рд╕рд╛рдорд╛рдпрд┐рдХ рдХрдкрд╛рдЯ, рдПрдХ рдЦрд┐рдбрдХреА, рдХреА рдиреЛрдЯрд┐рд╢реАрдЪреА рдкреНрд░рдд

ЁЯУН рддреБрдореНрд╣реА рдЗрдереЗ рдЖрд╣рд╛рдд: 12 рдкреИрдХреА рдзрдбрд╛ 08 ┬╖ рдорд╛рдЧрдЪрд╛: lesson-07-events-cqrs ┬╖ рдкреБрдврдЪрд╛: lesson-09-fitness-functions


ЁЯУж рдпрд╛ рдмреНрд░рдБрдЪрдордзреНрдпреЗ рдХрд╛рдп рдЖрд╣реЗ

рдзрдбреЗ 01тАУ07, рдЖрдгрд┐ рдмрд╣реБрддреЗрдХ рд▓реЛрдХ рд╡рд┐рд╕рд░рддрд╛рдд рддреА рдорд╛рд░реНрдЧрд┐рдХрд╛: data. рджреЛрди рд╡рд┐рднрд╛рдЧ code рдордзреНрдпреЗ рдкреВрд░реНрдгрдкрдгреЗ рд╡реЗрдЧрд│реЗ рдЕрд╕реВрдирд╣реА рдПрдХрд╛рдЪ рд╕рд╛рдорд╛рдпрд┐рдХ table рдореБрд│реЗ рдПрдХрдореЗрдХрд╛рдВрдирд╛ рдЪрд┐рдХрдЯрд▓реЗрд▓реЗ рдЕрд╕реВ рд╢рдХрддрд╛рдд. рджреБрд╕рд▒реНрдпрд╛ рд╡рд┐рднрд╛рдЧрд╛рдЪрд╛ data рдорд┐рд│рд╡рдгреНрдпрд╛рдЪреЗ рддреАрди рдорд╛рд░реНрдЧ тАФ shared database, API, рдХрд┐рдВрд╡рд╛ events (рд╕реНрд╡рддрдГрдЪреА copy рдареЗрд╡рд╛) тАФ рдЖрдгрд┐ рдорд╛рд▓рдХ рд╡рд┐рднрд╛рдЧ рдЖрдкрд▓рд╛ data рдмрджрд▓рддреЛ рдХрд┐рдВрд╡рд╛ рдмрдВрдж рдкрдбрддреЛ рддреЗрд╡реНрд╣рд╛ рдкреНрд░рддреНрдпреЗрдХрд╛рдЪреА рдХрд┐рдВрдордд рдХрд╛рдп. integration() arch/models.py рдордзреНрдпреЗ рдЖрдгрд┐ data() arch/demo.py рдордзреНрдпреЗ.

ЁЯзТ 5 рд╡рд░реНрд╖рд╛рдВрдЪреНрдпрд╛ рдореБрд▓рд╛рд▓рд╛ рд╕рдордЬрд╛рд╡рд▓реНрдпрд╛рд╕рд╛рд░рдЦреЗ

Fee рдЪреНрдпрд╛ рдкрд╛рд╡рддреНрдпрд╛ рдЫрд╛рдкрд╛рдпрд▓рд╛ рд╣рд┐рд╢реЛрдм рд╡рд┐рднрд╛рдЧрд╛рд▓рд╛ рдкреНрд░рддреНрдпреЗрдХ рдореБрд▓рд╛рдЪреЗ рдирд╛рд╡ рд╣рд╡реЗ рдЕрд╕рддреЗ. ЁЯз╛ рддреАрди рдорд╛рд░реНрдЧ:

  1. рд╕рд╛рдорд╛рдпрд┐рдХ рдХрдкрд╛рдЯ. рд╣рд┐рд╢реЛрдм рд╡рд┐рднрд╛рдЧ admissions office рдордзреНрдпреЗ рдЬрд╛рддреЛ рдЖрдгрд┐ admissions рдЪреА рдиреЛрдВрджрд╡рд╣реА рдереЗрдЯ рд╡рд╛рдЪрддреЛ. рд╕реЛрдкреЗ! рдкрдг рдПрдХреЗ рджрд┐рд╡рд╢реА admissions "name" column рдЪреЗ рдирд╛рд╡ рдмрджрд▓реВрди "full name" рдХрд░рддреЗ. рд╣рд┐рд╢реЛрдм рд╡рд┐рднрд╛рдЧрд╛рдЪреНрдпрд╛ рдкрд╛рд╡рддреНрдпрд╛ рдореЛрдбрддрд╛рдд тАФ рдЖрдгрд┐ рд╣рд┐рд╢реЛрдм рд╡рд┐рднрд╛рдЧрд╛рдиреЗ рдХрд╛рд╣реАрдЪ рдЪреВрдХ рдХреЗрд▓реЗрд▓реА рдирд╕рддреЗ.
  2. рдЦрд┐рдбрдХреАрд╡рд░ рд╡рд┐рдЪрд╛рд░рд╛. рд╣рд┐рд╢реЛрдм рд╡рд┐рднрд╛рдЧ admissions рдЪреНрдпрд╛ рдЦрд┐рдбрдХреАрд╡рд░ рд╡рд┐рдЪрд╛рд░рддреЛ: "roll number 7 рдЪреЗ рдирд╛рд╡ рд╕рд╛рдВрдЧрд╛рд▓ рдХрд╛?" Admissions рдЖрдкрд▓реА рдиреЛрдВрджрд╡рд╣реА рд╣рд╡реА рддрд╢реА рдкреБрдиреНрд╣рд╛ рдорд╛рдВрдбреВ рд╢рдХрддреЗ; рдЦрд┐рдбрдХреАрд╡рд░рдЪреЗ рдЙрддреНрддрд░ рддреЗрдЪ рд░рд╛рд╣рддреЗ. рдкрдг admissions office рдмрдВрдж рдЕрд╕рд▓реЗ рдХреА рд╣рд┐рд╢реЛрдм рд╡рд┐рднрд╛рдЧрд╛рд▓рд╛ рдЙрддреНрддрд░ рдорд┐рд│рдд рдирд╛рд╣реА.
  3. рдиреЛрдЯрд┐рд╢реАрдЪреА рдкреНрд░рдд рдХрд░рд╛. Admissions рдкреНрд░рддреНрдпреЗрдХ рд╡реЗрд│реА рдирд╛рд╡ рдмрджрд▓рд▓реЗ рдХреА рдПрдХ рд╕реВрдЪрдирд╛ рд▓рд╛рд╡рддреЗ. рд╣рд┐рд╢реЛрдм рд╡рд┐рднрд╛рдЧ рддреА рд╕реНрд╡рддрдГрдЪреНрдпрд╛ рдЫреЛрдЯреНрдпрд╛ рд╡рд╣реАрдд рдЙрддрд░рд╡реВрди рдШреЗрддреЛ. Admissions рдмрдВрдж рдЕрд╕рд▓реЗ рддрд░реА тАФ рд╣рд┐рд╢реЛрдм рд╡рд┐рднрд╛рдЧрд╛рдХрдбреЗ рдирд╛рд╡реЗ рдЕрд╕рддрд╛рдд. рдкрдг рдмрджрд▓ рдЭрд╛рд▓реНрдпрд╛рд╡рд░ рд▓рдЧреЗрдЪ, рд╣рд┐рд╢реЛрдм рд╡рд┐рднрд╛рдЧрд╛рдЪреА рд╡рд╣реА рдереЛрдбреА рдорд╛рдЧреЗ рдЕрд╕рддреЗ.

рдХреЛрдгрддрд╛рдЪ рдорд╛рд░реНрдЧ рдлреБрдХрдЯ рдирд╛рд╣реА. рдХреЛрдгрддреА рдЕрдбрдЪрдг рддреБрдореНрд╣рд╛рд▓рд╛ рдЪрд╛рд▓реЗрд▓ рддреЗ рддреБрдореНрд╣реА рдирд┐рд╡рдбрддрд╛.

ЁЯЧ║я╕П рдЖрдХреГрддреА

flowchart LR
    subgraph shared["ЁЯЧДя╕П shared database"]
      fee1["ЁЯТ░ fees"] -->|"SELECT name FROM students"| tbl[("admissions table")]
      gr1["ЁЯУЭ grades"] -->|"SELECT name тАж"| tbl
    end
    subgraph api["ЁЯкЯ API"]
      fee2["ЁЯТ░ fees"] -->|"GET /students/7"| adm2["ЁЯЧВя╕П admissions"]
    end
    subgraph ev["ЁЯУМ events"]
      adm3["ЁЯЧВя╕П admissions"] -->|"StudentRenamed"| copy[("fees' own copy")]
    end
    r["rename at tick 5, down ticks 10тАУ12:<br/>shared DB 15 failed, 2 teams broken ┬╖ API 3 failed ┬╖ events 2 stale"]

ЁЯЧ║я╕П рдХрд╛рдврд▓реЗрд▓реА рдЖрдХреГрддреА + рдПрдХ lab: https://school-edh.pages.dev/software-architecture/lesson-diagrams.html#l08

тЭУ рдХрд╛рдп

ЁЯдФ рдХрд╛

рдХрд╛рд░рдг shared table рд╣реА рд╕рд░реНрд╡рд╛рдд рд░реБрдВрдж рдорд╛рд░реНрдЧрд┐рдХрд╛ рдЖрд╣реЗ тАФ рдкреНрд░рддреНрдпреЗрдХ рд╡рд╛рдЪрдХ рдорд╛рд▓рдХрд╛рдЪреЗ рдЦрд╛рдЬрдЧреА model import рдХрд░рддреЛ, рдЖрдгрд┐ рдХреЛрдгрддрд╛рд╣реА import checker рддреЗ рдкрд╛рд╣реВ рд╢рдХрдд рдирд╛рд╣реА. "Microservices" рдордзреНрдпреЗ рдХреЗрд▓реЗрд▓реА рд╡рд┐рднрд╛рдЧрдгреА рд╢реЗрд╡рдЯреА distributed monolith рдирд┐рдШрдгреНрдпрд╛рдЪреЗ (рдзрдбрд╛ 06) рд╣реЗрдЪ рд╕рд░реНрд╡рд╛рдд рд╕рд╛рдорд╛рдиреНрдп рдХрд╛рд░рдг рдЖрд╣реЗ. рд╣реЗрддреБрдкреВрд░реНрд╡рдХ ownership рдирд┐рд╡рдбрд▓реНрдпрд╛рдиреЗ рдХреЛрдгрд╛рд▓рд╛ meeting рд╢рд┐рд╡рд╛рдп рдХрд╛рдп рдмрджрд▓рдгреНрдпрд╛рдЪреА рдкрд░рд╡рд╛рдирдЧреА рдЖрд╣реЗ рд╣реЗ рдард░рддреЗ.

ЁЯФз рдХрд╕реЗ (рдпрд╛ repo рдордзреНрдпреЗ)

arch/models.py рдордзрд▓реЗ integration(style, ticks, rename_at, down, lag, readers) рдПрдХ рд╢рд┐рдХрд╡рдгреНрдпрд╛рд╕рд╛рдареАрдЪреЗ model рдЪрд╛рд▓рд╡рддреЗ: fees 20 ticks рд╕рд╛рдареА рдкреНрд░рддреНрдпреЗрдХ tick рд▓рд╛ рдПрдХрджрд╛ рд╡рд┐рджреНрдпрд╛рд░реНрдерд┐рдиреАрдЪреЗ рдирд╛рд╡ рд╡рд╛рдЪрддреЗ; tick 5 рд▓рд╛ admissions name рдЪреЗ рдирд╛рд╡ рдмрджрд▓реВрди full_name рдХрд░рддреЗ; ticks 10тАУ12 рдордзреНрдпреЗ admissions рдмрдВрдж рдЕрд╕рддреЗ. Shared database: рдирд╛рд╡ рдмрджрд▓рд▓реНрдпрд╛рдирдВрддрд░рдЪреЗ рдкреНрд░рддреНрдпреЗрдХ рд╡рд╛рдЪрди рдЕрдкрдпрд╢реА рд╣реЛрддреЗ (рд╡рд╛рдЪрдХрд╛рдВрдЪреЗ SQL рдЕрдЬреВрдирд╣реА name рдореНрд╣рдгрддреЗ) рдЖрдгрд┐ readers рдЯреАрдореНрд╕рдЪрд╛ code рдореЛрдбрддреЛ. API: admissions рдмрдВрдж рдЕрд╕рддрд╛рдирд╛рдЪ рд╡рд╛рдЪрди рдЕрдкрдпрд╢реА рд╣реЛрддреЗ. Events: рдХрд╛рд╣реАрдЪ рдЕрдкрдпрд╢реА рд╣реЛрдд рдирд╛рд╣реА; рдмрджрд▓рд╛рдирдВрддрд░ lag ticks рдкрд░реНрдпрдВрдд рд╡рд╛рдЪрди stale рдЕрд╕рддреЗ.

ЁЯзк рдХрд░реВрди рдкрд╛рд╣рд╛

python3 arch/demo.py data
python3 - <<'EOF'
import sys; sys.path.insert(0, "arch"); from models import integration
print("API, admissions down 10 ticks  тЖТ", integration("API", down=(10, 20)))
print("events, 5 ticks of lag         тЖТ", integration("events", lag=5))
print("shared DB, 5 teams read it     тЖТ", integration("shared database", readers=5))
print("shared DB, rename at tick 19   тЖТ", integration("shared database", rename_at=19))
EOF

тЬЕ рддрдкрд╛рд╕рд╛ тАФ рддреБрдореНрд╣рд╛рд▓рд╛ рдХрд╛рдп рджрд┐рд╕рд╛рдпрд▓рд╛ рд╣рд╡реЗ

data рд╣реЗ рдЫрд╛рдкрддреЗ:

тФАтФА fees needs a student's name 20 times; at tick 5 admissions renames name тЖТ full_name; ticks 10тАУ12 admissions is down
   shared database  failed reads 15 ┬╖ stale reads 0 ┬╖ other teams' code broken by the rename 2
   API              failed reads  3 ┬╖ stale reads 0 ┬╖ other teams' code broken by the rename 0
   events           failed reads  0 ┬╖ stale reads 2 ┬╖ other teams' code broken by the rename 0

рддреБрдордЪрд╛ snippet рд╣реЗ рдЫрд╛рдкрддреЛ:

API, admissions down 10 ticks  тЖТ {'failed': 10, 'stale': 0, 'broken': 0}
events, 5 ticks of lag         тЖТ {'failed': 0, 'stale': 5, 'broken': 0}
shared DB, 5 teams read it     тЖТ {'failed': 15, 'stale': 0, 'broken': 5}
shared DB, rename at tick 19   тЖТ {'failed': 1, 'stale': 0, 'broken': 2}

ЁЯПБ рддреБрдореНрд╣реА рдЖрддреНрддрд╛рдЪ рдХрд╛рдп рд╕рд┐рджреНрдз рдХреЗрд▓реЗ

рдкреНрд░рддреНрдпреЗрдХ рд╢реИрд▓реА рдЖрдкрд▓реНрдпрд╛ рдкрджреНрдзрддреАрдиреЗ рдЕрдкрдпрд╢реА рд╣реЛрддреЗ, рдЖрдгрд┐ рдкреНрд░рддреНрдпреЗрдХ knob рдлрдХреНрдд рдЖрдкрд▓реАрдЪ рдХрд┐рдВрдордд рд╣рд▓рд╡рддреЛ. Admissions рдмрдВрдж рдЖрд╣реЗ рдпрд╛рдЪреА shared database рд▓рд╛ рдкрд░реНрд╡рд╛ рдирд╡реНрд╣рддреА тАФ рдкрдг рдПрдХрд╛ рдирд╛рд╡-рдмрджрд▓рд╛рдиреЗ рдкреНрд░рддреНрдпреЗрдХ рд╡рд╛рдЪрдХ рдореЛрдбрд▓рд╛ (2, рдордЧ 5 рдЯреАрдореНрд╕), рдЖрдгрд┐ рдкреНрд░рддреНрдпреЗрдХ рдЯреАрдо рдЖрдкрд▓реЗ SQL рджреБрд░реБрд╕реНрдд рдХрд░реЗрдкрд░реНрдпрдВрдд рддреЗ рдЕрдкрдпрд╢реА рд╣реЛрдд рд░рд╛рд╣рддреЗ. API рдирд╛рд╡-рдмрджрд▓рд╛рддреВрди рдЯрд┐рдХрд▓реЗ рдЖрдгрд┐ рдорд╛рд▓рдХ рдмрдВрдж рдЕрд╕рддрд╛рдирд╛ рдиреЗрдордХреЗ рддреЗрд╡рдвреЗрдЪ рдЕрдкрдпрд╢реА рдЭрд╛рд▓реЗ (3 ticks, рдордЧ 10). Events рдХрдзреАрдЪ рдЕрдкрдпрд╢реА рдЭрд╛рд▓реЗ рдирд╛рд╣реАрдд, рдкрдг lag рдЪрд╛ рдкреНрд░рддреНрдпреЗрдХ tick рдореНрд╣рдгрдЬреЗ рдПрдХ stale рд╡рд╛рдЪрди (2, рдордЧ 5). рдХреЛрдгрддрд╛рд╣реА "рдмрд░реЛрдмрд░" рдирд╛рд╣реА: fee рдкрд╛рд╡рддреАрд▓рд╛ 2 ticks рдЬреБрдиреЗ рдирд╛рд╡ рдЪрд╛рд▓реВ рд╢рдХрддреЗ; payment balance рд▓рд╛ рдмрд╣реБрдзрд╛ рдирд╛рд╣реА.

тЪая╕П рдиреЗрд╣рдореАрдЪреНрдпрд╛ рдЪреБрдХрд╛

ЁЯПн рдкреНрд░рддреНрдпрдХреНрд╖ рд╡рд╛рдкрд░рд╛рдд

рдЦрд▒реНрдпрд╛ project рд╡рд░ тАФ рдПрдХрдЪ PostgreSQL server рдЕрд╕рддрд╛рдирд╛рд╣реА рддреБрдореНрд╣реА ownership рдЦрд░реА рдХрд░реВ рд╢рдХрддрд╛: рдкреНрд░рддреНрдпреЗрдХ рд╡рд┐рднрд╛рдЧрд╛рд╕рд╛рдареА рдПрдХ schema, рдЖрдгрд┐ рдЗрддрд░ рдлрдХреНрдд рд╡рд╛рдЪреВ рд╢рдХрддреАрд▓ рдЕрд╕реЗ рдПрдХрдЪ versioned view:

CREATE SCHEMA admissions;
CREATE TABLE admissions.students (form_no int PRIMARY KEY, full_name text, status text);
CREATE VIEW admissions.students_v1 AS
  SELECT form_no, full_name AS name FROM admissions.students;    -- the published contract
REVOKE ALL ON ALL TABLES IN SCHEMA admissions FROM fees_app;
GRANT USAGE ON SCHEMA admissions TO fees_app;
GRANT SELECT ON admissions.students_v1 TO fees_app;

рдЖрддрд╛ admissions рдЖрдкрд▓реНрдпрд╛ column рдЪреЗ рдирд╛рд╡ рдмрджрд▓реВ рд╢рдХрддреЗ рдЖрдгрд┐ view рдордзрд▓реЗ name рддрд╕реЗрдЪ рдареЗрд╡реВ рд╢рдХрддреЗ тАФ lab рдордзреНрдпреЗ 2 рдЯреАрдореНрд╕ рдореЛрдбрдгрд╛рд░рд╛ рдирд╛рд╡-рдмрджрд▓ рдЖрддрд╛ рдХреЛрдгрд╛рд▓рд╛рдЪ рдореЛрдбрдд рдирд╛рд╣реА. рд▓рд┐рд╣реВрди рдареЗрд╡рд▓реЗрд▓рд╛ API рдХрд░рд╛рд░ (OpenAPI) HTTP рд╕рд╛рдареА рд╣реЗрдЪ рдХрд╛рдо рдХрд░рддреЛ:

paths:
  /students/{roll_no}:
    get:
      responses:
        "200":
          content:
            application/json:
              schema: { type: object, properties: { roll_no: { type: integer }, name: { type: string } } }

ЁЯПн рдкреНрд░рддреНрдпрдХреНрд╖ рд╡рд╛рдкрд░рд╛рдд рд╣реЗ рдХрд╛ рдорд╣рддреНрддреНрд╡рд╛рдЪреЗ: рдкреНрд░рддреНрдпреЗрдХ table рдЖрдгрд┐ рддреНрдпрд╛рдЪрд╛ рдПрдХрдЪ рдорд╛рд▓рдХ рдпрд╛рдВрдЪреА рдпрд╛рджреА рдХрд░рд╛. рджреЛрди рд▓рд┐рд╣рд┐рдгрд╛рд░реЗ рдЕрд╕рд▓реЗрд▓рд╛, рдХрд┐рдВрд╡рд╛ рдорд╛рд▓рдХ рд╡рд┐рднрд╛рдЧрд╛рдмрд╛рд╣реЗрд░ рд╡рд╛рдЪрдХ рдЕрд╕рд▓реЗрд▓рд╛ рдХреЛрдгрддрд╛рд╣реА table рд╣реА рд▓рдкрд▓реЗрд▓реА рдорд╛рд░реНрдЧрд┐рдХрд╛ рдЖрд╣реЗ тАФ рддреНрдпрд╛рд▓рд╛ рд╣реЗрддреБрдкреВрд░реНрд╡рдХ view, API рдХрд┐рдВрд╡рд╛ event рджреНрдпрд╛.

тПня╕П рдкреБрдвреЗ

рдЖрддрд╛ рдЖрдкрд▓реНрдпрд╛рд▓рд╛ рдирд┐рдпрдо рдорд╛рд╣реАрдд рдЖрд╣реЗрдд: cycles рдирд╛рд╣реАрдд, arrows рдЖрддрд▓реНрдпрд╛ рджрд┐рд╢реЗрдиреЗ, рд╡рд┐рднрд╛рдЧ рд╡реЗрдЧрд│реЗ, data рд▓рд╛ рдорд╛рд▓рдХ. рд╢реБрдХреНрд░рд╡рд╛рд░реА рджреБрдкрд╛рд░реА рддреЗ рдХреЛрдг рддрдкрд╛рд╕рдгрд╛рд░? Fitness functions тАФ CI рдордзреАрд▓ architecture tests.

git checkout lesson-09-fitness-functions

ЁЯЧДя╕П Lesson 08 тАФ Data ownership: a shared cupboard, a counter, or a copied notice

ЁЯУН You are here: Lesson 08 of 12 ┬╖ Previous: lesson-07-events-cqrs ┬╖ Next: lesson-09-fitness-functions


ЁЯУж What's in this branch

Lessons 01тАУ07, plus the corridor most people forget: data. Two departments can be perfectly separate in code and still be glued together through one shared table. Three ways to get another department's data тАФ a shared database, an API, or events (keep your own copy) тАФ and what each one costs when the owner changes its data or goes down. integration() in arch/models.py and data() in arch/demo.py.

ЁЯзТ Explain like I'm 5

The accounts office needs each child's name to print fee receipts. ЁЯз╛ Three ways:

  1. The shared cupboard. Accounts walks into the admissions office and reads the admissions register directly. Easy! But one day admissions renames the column "name" to "full name". Accounts' receipts break тАФ and accounts did nothing wrong.
  2. Ask at the counter. Accounts asks at the admissions window: "name of roll number 7, please?" Admissions can reorganise its register however it likes; the answer at the window stays the same. But when the admissions office is closed, accounts gets no answer.
  3. Copy the notice. Every time admissions changes a name, it pins a notice. Accounts copies it into its own little book. Admissions can be closed тАФ accounts still has the names. But right after a change, accounts' book is a bit behind.

No way is free. You choose which problem you would rather have.

ЁЯЧ║я╕П Diagram

flowchart LR
    subgraph shared["ЁЯЧДя╕П shared database"]
      fee1["ЁЯТ░ fees"] -->|"SELECT name FROM students"| tbl[("admissions table")]
      gr1["ЁЯУЭ grades"] -->|"SELECT name тАж"| tbl
    end
    subgraph api["ЁЯкЯ API"]
      fee2["ЁЯТ░ fees"] -->|"GET /students/7"| adm2["ЁЯЧВя╕П admissions"]
    end
    subgraph ev["ЁЯУМ events"]
      adm3["ЁЯЧВя╕П admissions"] -->|"StudentRenamed"| copy[("fees' own copy")]
    end
    r["rename at tick 5, down ticks 10тАУ12:<br/>shared DB 15 failed, 2 teams broken ┬╖ API 3 failed ┬╖ events 2 stale"]

ЁЯЧ║я╕П Drawn version + a lab: https://school-edh.pages.dev/software-architecture/lesson-diagrams.html#l08

тЭУ What

ЁЯдФ Why

Because a shared table is the widest corridor there is тАФ every reader imports the owner's private model, and no import checker can see it. It is also the most common reason a "microservices" split turns out to be a distributed monolith (lesson 06). Choosing ownership on purpose decides who is allowed to change what without a meeting.

ЁЯФз How (in this repo)

integration(style, ticks, rename_at, down, lag, readers) in arch/models.py plays out a teaching model: fees reads a student's name once per tick for 20 ticks; at tick 5 admissions renames name to full_name; ticks 10тАУ12 admissions is down. Shared database: every read after the rename fails (the readers' SQL still says name) and readers teams' code breaks. API: reads fail only while admissions is down. Events: nothing fails; reads are stale for lag ticks after the change.

ЁЯзк Try it

python3 arch/demo.py data
python3 - <<'EOF'
import sys; sys.path.insert(0, "arch"); from models import integration
print("API, admissions down 10 ticks  тЖТ", integration("API", down=(10, 20)))
print("events, 5 ticks of lag         тЖТ", integration("events", lag=5))
print("shared DB, 5 teams read it     тЖТ", integration("shared database", readers=5))
print("shared DB, rename at tick 19   тЖТ", integration("shared database", rename_at=19))
EOF

тЬЕ Verify тАФ what you should see

data prints:

тФАтФА fees needs a student's name 20 times; at tick 5 admissions renames name тЖТ full_name; ticks 10тАУ12 admissions is down
   shared database  failed reads 15 ┬╖ stale reads 0 ┬╖ other teams' code broken by the rename 2
   API              failed reads  3 ┬╖ stale reads 0 ┬╖ other teams' code broken by the rename 0
   events           failed reads  0 ┬╖ stale reads 2 ┬╖ other teams' code broken by the rename 0

Your snippet prints:

API, admissions down 10 ticks  тЖТ {'failed': 10, 'stale': 0, 'broken': 0}
events, 5 ticks of lag         тЖТ {'failed': 0, 'stale': 5, 'broken': 0}
shared DB, 5 teams read it     тЖТ {'failed': 15, 'stale': 0, 'broken': 5}
shared DB, rename at tick 19   тЖТ {'failed': 1, 'stale': 0, 'broken': 2}

ЁЯПБ What you just proved

Each style fails in its own way, and each knob moves only its own cost. The shared database did not care that admissions was down тАФ but one rename broke every reader (2, then 5 teams), and it keeps failing until each team fixes its SQL. The API survived the rename and failed exactly while the owner was down (3 ticks, then 10). Events never failed, but every tick of lag is a stale read (2, then 5). None is "right": a fee receipt can live with a name that is 2 ticks old; a payment balance probably cannot.

тЪая╕П Common mistakes

ЁЯПн In production

On a real project тАФ even with one PostgreSQL server, you can make ownership real: one schema per department, and a versioned view as the only thing others may read:

CREATE SCHEMA admissions;
CREATE TABLE admissions.students (form_no int PRIMARY KEY, full_name text, status text);
CREATE VIEW admissions.students_v1 AS
  SELECT form_no, full_name AS name FROM admissions.students;    -- the published contract
REVOKE ALL ON ALL TABLES IN SCHEMA admissions FROM fees_app;
GRANT USAGE ON SCHEMA admissions TO fees_app;
GRANT SELECT ON admissions.students_v1 TO fees_app;

Now admissions can rename its column and keep the view's name тАФ the rename that broke 2 teams in the lab breaks nobody. An API contract written down (OpenAPI) does the same job for HTTP:

paths:
  /students/{roll_no}:
    get:
      responses:
        "200":
          content:
            application/json:
              schema: { type: object, properties: { roll_no: { type: integer }, name: { type: string } } }

ЁЯПн Why this matters in production: list every table and its one owner. Any table with two writers, or readers outside the owning department, is a hidden corridor тАФ give it a view, an API or an event, on purpose.

тПня╕П Next

We now know the rules: no cycles, arrows inward, departments apart, data owned. Who checks them on a Friday afternoon? Fitness functions тАФ architecture tests in CI.

git checkout lesson-09-fitness-functions
тЖР Previousevents cqrsNext тЖТfitness functions

This page is the lesson's README from the lesson-08-data-ownership branch, shown here so the whole School stays on one site. Code files open on GitHub at the same branch.