Chapterبەش 4 · Lectureوانە 4.1

Layers and Business Logic: Where Each Rule Livesچینەکان و لۆجیکی کاروبار: هەر یاسایەک لە کوێ دەژی

Why an all-in-one program hurts; business logic versus presentation and data access; the three layers (UI, business logic, data access) and the one-way dependency rule; layers versus tiers; refactoring Bazaar Cart into a repository, a service and two presentations in Python; testing the rules with a fake repository; where validation belongs; layers in PHP and mobile apps; DRY, coupling and cohesion; clean architecture as an extra; and when not to layer.

بۆچی هەموو شت لە یەکدا ئازار دەدات؛ بەرانبەر پیشاندان و دەستڕاگەیشتن بە ؛ سێ (، ، ) و یاسای یەک ئاراستە؛ بەرانبەر چینی ئامێر؛ ڕێکخستنەوەی Bazaar Cart بۆ ، و دوو لە Pythonـدا؛ تاقیکردنەوەی یاساکان بە ساختە؛ لە کوێ بێت؛ لە PHP و ئەپی مۆبایلدا؛ DRY، و ؛ تەلارسازیی Clean وەک زیادە؛ و کەی چینبەندی مەکە.

24 min read23 خولەک خوێندنەوە · 20 stepsهەنگاو

What you will learnچی فێر دەبیت

  • Tell business logic apart from presentation logic and data access in real code.
  • Explain the three layers, the one-way dependency rule and the difference between layers and tiers.
  • Refactor a small Python program into a repository, a service and a presentation, and add a second presentation.
  • Test business rules alone with a fake data layer, and place validation in the right layers.
  • Judge when layering helps (DRY, coupling, cohesion) and when it is too much.
  • لە کۆدی ڕاستەقینەدا لە لۆجیکی پیشاندان و دەستڕاگەیشتن بە جیا بکەیتەوە.
  • سێ ، یاسای یەک ئاراستە و جیاوازیی نێوان و چینی ئامێر ڕوون بکەیتەوە.
  • بچووکی Python بۆ ، و ڕێک بخەیتەوە، پاشان دووەم زیاد بکەیت.
  • یاساکانی کار بە ساختە بە تەنیا تاقی بکەیتەوە و لە دروستەکاندا دابنێیت.
  • هەڵبسەنگێنیت کەی چینبەندی یارمەتی دەدات (DRY، ، ) و کەی زیادە.

One file that does everythingیەک فایل کە هەموو شتێک دەکات

Karwan's first Bazaar Cart program for Soran's shop is one short file. It asks for a product and the grams, reads the price from an SQLite database, applies the shop's rules and prints the bill. The prices are made up, in Iraqi dinars (IQD). Here it is, with a sample run:

یەکەم Bazaar Cart کە کاروان بۆ دوکانی سۆرانی نووسی، یەک کورتە. بەرهەم و گرام دەپرسێت، نرخەکە لە دەخوێنێتەوە، یاساکانی دوکان جێبەجێ دەکات و پسوولەکە چاپ دەکات. نرخەکان دروستکراون و بە دیناری عێراقین ⁦(IQD)⁩. ئەمە کۆدەکە و نموونەیەکی کارپێکردنییەتی:

Everything in one file (Python 3.13)هەموو شت لە یەک فایلدا ⁦(Python 3.13)⁩all_in_one.pyPYTHONType in a terminalلە تێرمیناڵدا بینووسە
import math
import sqlite3

db = sqlite3.connect(":memory:")
db.execute("CREATE TABLE products (code TEXT, name TEXT, price_per_kg INT, stock_g INT)")
db.executemany("INSERT INTO products VALUES (?, ?, ?, ?)", [
    ("walnuts", "Walnuts", 18000, 20000), ("sumac", "Sumac", 8000, 5000),
    ("figs", "Dried figs", 12000, 8000), ("tea", "Black tea", 14000, 10000),
    ("honey", "Honey", 30000, 3000)])

goods = 0
while True:
    code = input("Product code (empty to finish): ")
    if code == "":
        break
    grams = int(input("Grams: "))
    row = db.execute("SELECT name, price_per_kg, stock_g FROM products WHERE code = ?",
                     (code,)).fetchone()
    name, price_per_kg, stock_g = row
    if grams < 100 or grams > 10000 or grams > stock_g:
        print("  Not allowed")
        continue
    price = math.ceil(price_per_kg * grams / 1000 / 250) * 250
    db.execute("UPDATE products SET stock_g = stock_g - ? WHERE code = ?", (grams, code))
    goods += price
    print(f"  {name} {grams} g: {price:,} IQD")
delivery = 0 if goods >= 50000 else 3000
print(f"Goods {goods:,} IQD + delivery {delivery:,} IQD = {goods + delivery:,} IQD")

It works, and for one person on one laptop it is fine. Then the requests arrive. Soran wants orders from a web page too: the rules would have to be copied into it. Dilan wants an automatic check that delivery is free from 50,000 IQD: she cannot test that without typing into input(). Aram wants the products on a server database: every SQL line is mixed with print() calls. And when the shop refuses honey 50 g, the customer only reads «Not allowed».

کار دەکات و بۆ یەک کەس لەسەر یەک باشە. پاشان داواکارییەکان دێن. سۆران دەیەوێت داواکاری لە لاپەڕەتۆڕێکیشەوە وەربگرێت: دەبێت یاساکان کۆپی بکرێن بۆ ئەوێ. دیلان دەیەوێت بە شێوەیەکی خۆکار بپشکنێت کە گەیاندن لە ⁦50,000⁩ دینارەوە بێبەرانبەرە: بێ تایپکردن لە input()ـدا ناتوانێت تاقی بکاتەوە. ئارام دەیەوێت بەرهەمەکان ببات بۆ لەسەر : هەموو دێڕێکی تێکەڵی print() بووە. کاتێکیش دوکان ⁦50⁩ گرام هەنگوین ڕەت دەکاتەوە، کڕیار تەنیا «Not allowed» دەبینێت.

One box, all_in_one.py, full of mixed coloured lines: blue for asking and showing, green for the shop's rules, orange for SQL. Three people send requests into the same file: Soran wants a web page for orders, Dilan wants to test free delivery without typing, Aram wants the products moved to a server database. Bottom: every change opens the same file and can break the other two jobs.چوارگۆشەیەک بە ناوی all_in_one.py پڕ لە دێڕی ڕەنگاوڕەنگی تێکەڵ: شین بۆ پرسین و پیشاندان، سەوز بۆ یاساکانی دوکان، پرتەقاڵی بۆ SQL. سێ کەس داواکاری دەنێرن بۆ هەمان فایل: سۆران لاپەڕەتۆڕێک بۆ داواکاری دەوێت، دیلان دەیەوێت گەیاندنی بێبەرانبەر بێ تایپکردن تاقی بکاتەوە، ئارام دەیەوێت بەرهەمەکان ببات بۆ بنکەدراوەیەک لەسەر ڕاژەکار. خوارەوە: هەر گۆڕانکارییەک هەمان فایل دەکاتەوە و دەتوانێت دوو کارەکەی تر تێک بدات.
Three kinds of change, one tangled fileسێ جۆر گۆڕانکاری، یەک تێکئاڵاوCêIT

This lesson shows where each piece belongs: the business logic (the shop's rules) in its own layer, between the screen and the database, and how that makes the program easier to test and to grow.

ئەم وانەیە پیشان دەدات هەر پارچەیەک شوێنی لە کوێیە: (یاساکانی دوکان) لە خۆیدا، لە نێوان شاشە و . هەروەها پیشان دەدات چۆن ئەمە ئاسانتر دەکات بۆ تاقیکردنەوە و گەورەبوون.

Business logic: the shop’s rulesلۆجیکی کاروبار: یاساکانی دوکان

Every program exists for a real job. The rules of that job are its business logic. For Bazaar Cart they are Soran's rules: R1 a line costs price per kg × grams ÷ 1000, rounded up to the next 250 IQD; R2 each line is 100 g to 10,000 g; R3 never more than the stock; R4 delivery inside Sulaimani costs 3,000 IQD and is free from 50,000 IQD of goods; and, in version 1.1.0, R5 a 5 % discount from 100,000 IQD. Opening hours or «only the owner may change prices» would be business rules too.

هەر بۆ کارێکی ڕاستەقینە هەیە. یاساکانی ئەو کارە ئەون. بۆ Bazaar Cart یاساکانی سۆرانن: ⁦R1⁩ نرخی دێڕێک = نرخی کیلۆیەک × گرام ÷ ⁦1000⁩، کە بۆ سەرەوە دەبرێت بۆ ⁦250⁩ دیناری دواتر ()؛ ⁦R2⁩ هەر دێڕێک ⁦100⁩ تا ⁦10,000⁩ گرامە؛ ⁦R3⁩ هەرگیز زیاتر لە بڕی کۆگا نا؛ ⁦R4⁩ گەیاندن لە ناو سلێمانی ⁦3,000⁩ دینارە و لە ⁦50,000⁩ دیناری کاڵاوە بێبەرانبەرە؛ لە وەشانی ⁦1.1.0⁩ـیشدا ⁦R5⁩ داشکاندنی 5 لە سەد لە ⁦100,000⁩ دینارەوە. کاتی کردنەوەی دوکان یان «تەنیا خاوەن دوکان دەتوانێت نرخ بگۆڕێت» یاسای کاریشن.

Definitionپێناسە

The rules of the real-world job that the software exists for — prices, discounts, delivery fees, stock, who may do what — kept apart from the screens and from the database code.

یاساکانی ئەو کارە ڕاستەقینەیەی بۆی دروست کراوە — نرخ، داشکاندن، کرێی گەیاندن، بڕی بەردەست، کێ دەتوانێت چی بکات — جیا لە شاشەکان و لە کۆدی .

Android's architecture guide says business logic is what gives an app its value. Showing the bill in green, or the SQL that reads a price, is not business logic: the first is presentation, the second is data access.

ڕێنمایی تەلارسازیی Android دەڵێت ئەوەیە کە بەهای ئەپەکە دروست دەکات. پیشاندانی پسوولە بە سەوز، یان ئەو نرخێک دەخوێنێتەوە، نییە: یەکەمیان پیشاندانە و دووەمیان دەستڕاگەیشتنە بە .

A pharmacyدەرمانخانە

In a pharmacy in the bazaar, the counter assistant talks to customers, takes the prescription and hands over the bag. The pharmacist checks the rules: is a prescription needed, is the dose safe, is the medicine on the shelf? The storeroom keeps the boxes and the stock book. An order that comes by phone or WhatsApp goes to the same pharmacist, so the rules are checked the same way.

لە دەرمانخانەیەکی بازاڕدا، یاریدەدەری مێزی پێشەوە قسە لەگەڵ کڕیار دەکات، ڕەچەتەکە وەردەگرێت و کیسەکە دەداتەوە. دەرمانساز یاساکان دەپشکنێت: ڕەچەتە پێویستە؟ ژەمەکە سەلامەتە؟ دەرمانەکە لە ڕەفدا هەیە؟ کۆگا قوتووەکان و دەفتەری کۆگا ڕادەگرێت. داواکارییەک کە بە تەلەفۆن یان واتسئاپ دێت، دەچێتە لای هەمان دەرمانساز، بۆیە یاساکان بە هەمان شێوە دەپشکنرێن.

Where the analogy stops workingسنووری ئەم نموونەیە

In a small pharmacy one person may do all three jobs. In a program the three jobs stay in separate code even when one developer writes all of them.

لە دەرمانخانەیەکی بچووکدا لەوانەیە یەک کەس هەر سێ کارەکە بکات. لە سێ کارەکە لە کۆدی جیادا دەمێننەوە، تەنانەت ئەگەر یەک هەموویان بنووسێت.

Three columns. Counter assistant (blue, user interface layer): talks to customers, takes the prescription, hands over the bag. Pharmacist (green, business logic layer): prescription needed? safe dose? is it on the shelf? Storeroom (orange, data access layer): shelves and the stock book, fetches boxes, notes what went out. Arrows go from counter to pharmacist to storeroom. A note below the pharmacist: phone and WhatsApp orders go to the same pharmacist.سێ ستوون. یاریدەدەری مێزی پێشەوە (شین، چینی نێوبەستەی بەکارهێنەر): قسە لەگەڵ کڕیار دەکات، ڕەچەتەکە وەردەگرێت، کیسەکە دەداتەوە. دەرمانساز (سەوز، چینی لۆجیکی کاروبار): ڕەچەتە پێویستە؟ ژەمەکە سەلامەتە؟ لە ڕەفدا هەیە؟ کۆگا (پرتەقاڵی، چینی دەستڕاگەیشتن بە دراوە): ڕەف و دەفتەری کۆگا، قوتوو دەهێنێت، ئەوەی دەرچوو دەنووسێت. تیرەکان لە مێزی پێشەوەوە دەچن بۆ دەرمانساز و پاشان بۆ کۆگا. تێبینییەک لە ژێر دەرمانسازەوە: داواکاریی تەلەفۆن و واتسئاپیش دەچێتە لای هەمان دەرمانساز.
Three jobs, three placesسێ کار، سێ شوێنCêIT

Business logic: the shop’s rulesلۆجیکی کاروبار: یاساکانی دوکان

Three kinds of code in one scriptسێ جۆر کۆد لە یەک دەسنووسدا

Colour each line of all_in_one.py by its job and the problem becomes visible. Blue lines ask and show (presentation), green lines are the shop's rules (business logic) and orange lines talk to the database (data access). The colours are mixed from top to bottom.

هەر دێڕێکی all_in_one.py بەپێی کارەکەی ڕەنگ بکە و کێشەکە دیار دەبێت. دێڕە شینەکان دەپرسن و پیشان دەدەن (پیشاندان)، دێڕە سەوزەکان یاساکانی دوکانن () و دێڕە پرتەقاڵییەکان قسە لەگەڵ دەکەن (دەستڕاگەیشتن بە ). ڕەنگەکان لە سەرەوە تا خوارەوە تێکەڵن.

The 28 lines of all_in_one.py with coloured backgrounds. Orange (data access): lines 4 to 9 create and fill the products table, lines 17 to 19 run SELECT and unpack the row, line 24 runs UPDATE. Green (business logic): line 11 goods = 0, line 20 the 100 to 10,000 g and stock check, line 23 the price rounded up to 250, line 25 goods += price, line 27 the delivery rule. Blue (presentation): the while loop, input() calls, the break, the print() calls and continue. Lines 1 to 3 and 10 have no colour.28 دێڕەکەی all_in_one.py بە باکگراوندی ڕەنگاوڕەنگ. پرتەقاڵی (دەستڕاگەیشتن بە دراوە): دێڕی 4 تا 9 خشتەی products دروست دەکەن و پڕی دەکەن، دێڕی 17 تا 19 SELECT جێبەجێ دەکەن و ڕیزەکە دەکەن بە سێ گۆڕاو، دێڕی 24 UPDATE جێبەجێ دەکات. سەوز (لۆجیکی کاروبار): دێڕی 11 goods = 0، دێڕی 20 پشکنینی 100 تا 10,000 گرام و بڕی کۆگا، دێڕی 23 نرخ، بۆ سەرەوە دەبرێت بۆ 250 (نزیککردنەوە)، دێڕی 25 goods += price، دێڕی 27 یاسای گەیاندن. شین (پیشاندان): خولی while، بانگکردنەکانی input()، break، بانگکردنەکانی print() و continue. دێڕی 1 تا 3 و 10 ڕەنگیان نییە.
The same script, coloured by jobهەمان ، بەپێی کار ڕەنگکراوCêIT
  • Presentation logic: how to ask, what to show, the words and the layout.
  • Business logic: what the shop allows and how it calculates money.
  • Data access: how products are stored and found: SQL, files or a web API.
  • لۆجیکی پیشاندان: چۆن بپرسیت، چی پیشان بدەیت، وشەکان و ڕێکخستنی شاشە.
  • : دوکان چی ڕێگە پێ دەدات و پارە چۆن دەژمێرێت.
  • دەستڕاگەیشتن بە : بەرهەمەکان چۆن هەڵدەگیرێن و دەدۆزرێنەوە: ، یان وێب.

Three layers and one directionسێ چین و یەک ئاراستە

The usual cure is to give each kind of code its own layer. Martin Fowler calls this presentation–domain–data layering, and Microsoft's architecture guide calls the layers UI, BLL and DAL:

چارەسەری باو ئەوەیە کە هەر جۆرە کۆدێک خۆی هەبێت. Martin Fowler ئەمە ناو دەنێت چینبەندیی پیشاندان، domain و و ڕێنمایی تەلارسازیی Microsoft بە دەڵێت UI، BLL و DAL:

LayerIts jobئەرکەکەیIn Bazaar Cartلە Bazaar Cartـدا
(presentation)Asks, shows, turns clicks and key presses into callsدەپرسێت، پیشان دەدات، و دوگمەلێدان دەکات بە بانگکردنcli.py
(domain)Applies the rules, decides what is allowed, calculatesیاساکان جێبەجێ دەکات، بڕیار دەدات چی ڕێگەپێدراوە، دەژمێرێتservices.py
Reads and writes stored data هەڵگیراو دەخوێنێتەوە و دەنووسێتrepository.py

The dependency rule: each layer calls only the layer below it, never the one above. The UI calls the business layer, the business layer calls the data layer, and results come back up as return values. The business layer never calls input() or print(), and the data layer never decides a price.

یاسای : هەر تەنیا ژێرەوەی خۆی بانگ دەکات، هەرگیز ئەوەی سەرەوە نا. بانگ دەکات، ئەویش و ئەنجامەکان وەک دەگەڕێنەوە سەرەوە. هەرگیز input() یان print() بانگ ناکات و هەرگیز بڕیاری نرخ نادات.

Two panels with the same three layers above an SQLite database: user interface layer (cli.py, receipt.py), business logic layer (services.py), data access layer (repository.py). Right: solid arrows go down one step at a time (calls) and dashed arrows come back up (results). Wrong: arrow 1 goes from the UI straight to the data layer (SQL in the UI, it skips a layer); arrow 2 goes up from the business layer to the UI (the rules call input()); arrow 3 goes up from the data layer to the UI (data code prints on the screen).دوو بەش بە هەمان سێ چین لە سەرووی بنکەدراوەیەکی SQLite: چینی نێوبەستەی بەکارهێنەر (cli.py، receipt.py)، چینی لۆجیکی کاروبار (services.py)، چینی دەستڕاگەیشتن بە دراوە (repository.py). دروست: تیری پڕ هەنگاو بە هەنگاو بەرەو خوار دەچێت (بانگکردن) و تیری خاڵخاڵ دەگەڕێتەوە سەرەوە (ئەنجام). هەڵە: تیری 1 لە چینی نێوبەستەی بەکارهێنەرەوە ڕاستەوخۆ دەچێت بۆ چینی دەستڕاگەیشتن بە دراوە (SQL لە چینی نێوبەستەی بەکارهێنەردا، چینێک باز دەدات)؛ تیری 2 لە چینی لۆجیکی کاروبارەوە بەرەو چینی نێوبەستەی بەکارهێنەر سەردەکەوێت (یاساکان input بانگ دەکەن)؛ تیری 3 لە چینی دەستڕاگەیشتن بە دراوەوە بەرەو چینی نێوبەستەی بەکارهێنەر سەردەکەوێت (کۆدی دراوە لەسەر شاشە چاپ دەکات).
The dependency rule: down onlyیاسای : تەنیا بەرەو خوارCêIT

Three layers and one directionسێ چین و یەک ئاراستە

Layers are not tiersچین و چینی ئامێر جیاوازن

A layer is how the code is organised. A tier is a machine or process where code runs. (In Kurdish both are often called «چین»; this lesson says «چینی کۆد» and «چینی ئامێر» to keep them apart.) Today Bazaar Cart has three layers in one tier: everything runs on one laptop, with SQLite in memory.

شێوەی ڕێکخستنی کۆدە. tier ئامێر یان پرۆسەیەکە کە کۆدی لەسەر کار دەکات. (لە کوردیدا زۆرجار بە هەردووکیان دەوترێت «چین»؛ ئەم وانەیە بۆ جیاکردنەوەیان دەڵێت «چینی کۆد» و «چینی ئامێر».) ئێستا Bazaar Cart سێ چینی کۆدی هەیە لە یەک چینی ئامێردا: هەموو شت لەسەر یەک کار دەکات و SQLite لە بیرگەدایە.

Left: Bazaar Cart today, three layers in one tier: one laptop and one program holding the user interface, business logic and data access layers, with sqlite3 in memory. Right: a web version with the same three layers in three tiers: a browser that shows the page, a web server that holds all three code layers, and a database server; the browser never talks to the database.چەپ: Bazaar Cart ئێستا، سێ چینی کۆد لە یەک چینی ئامێردا: لاپتۆپێک و بەرنامەیەک کە چینی نێوبەستەی بەکارهێنەر، چینی لۆجیکی کاروبار و چینی دەستڕاگەیشتن بە دراوە تێدایە، لەگەڵ sqlite3 لە بیرگەدا. ڕاست: وەشانی وێب بە هەمان سێ چینی کۆد لە سێ چینی ئامێردا: تۆڕگەڕ کە لاپەڕەتۆڕەکە پیشان دەدات، ڕاژەکاری وێب کە هەر سێ چینی کۆدەکەی تێدایە و ڕاژەکاری بنکەدراوە؛ تۆڕگەڕەکە هەرگیز قسە لەگەڵ بنکەدراوە ناکات.
The same layers can run on one machine or on threeهەمان دەتوانن لەسەر یەک ئامێر یان سێ ئامێر کار بکەنCêIT

A web version could put the same code on three machines: the browser, a web server and a database server. That is the three-tier architecture of the database course. Fowler stresses that the layers stay logical: you can run all three on a laptop or spread them out, and the code inside does not change.

وەشانێکی وێب دەتوانێت هەمان کۆد لەسەر سێ ئامێر دابنێت: ، و . ئەمە تەلارسازیی سێ‌چینیـی خولی . Fowler جەخت دەکاتەوە کە لۆجیکی دەمێننەوە: دەتوانیت هەر سێکیان لەسەر کار پێ بکەیت یان بڵاویان بکەیتەوە و کۆدی ناویان ناگۆڕێت.

From one script to a packageلە دەسنووسێکەوە بۆ پاکێجێک

Shilan splits the script into a small Python package, one file per job, plus a folder of tests. We build it from the bottom up: first the shared data shapes, then the data layer, then the rules, then the screens. The files of the earlier lessons are reorganised the same way (Versions and Builds): prices.py (the rules) becomes services.py, storage.py (SQLite) becomes repository.py, cart.py becomes cli.py, and models.py is new.

شیلان دابەش دەکات بۆ بچووکی Python، بۆ هەر کارێک یەک ، لەگەڵ بوخچەیەک بۆ . لە خوارەوە بۆ سەرەوە دروستی دەکەین: سەرەتا شێوەی هاوبەشی ، پاشان ، پاشان یاساکان، پاشان شاشەکان. وانەکانی پێشووش بە هەمان شێوە ڕێک دەخرێنەوە (وەشانەکان و بیلدەکان): prices.py (یاساکان) دەبێت بە services.py، storage.py () دەبێت بە repository.py، cart.py دەبێت بە cli.py و models.py نوێیە.

A folder tree. bazaar-cart contains bazaar_cart and tests. bazaar_cart holds __init__.py (marks the folder as a package), models.py (Product, OrderLine, Order: shared data), repository.py (data access layer: SQL), services.py (business logic layer: R1 to R4), cli.py (UI layer: asks and prints) and receipt.py (UI layer 2: a JSON receipt). tests holds an empty __init__.py (lets unittest find the tests) and test_services.py (tests the rules alone).دارێکی بوخچە. bazaar-cart بوخچەکانی bazaar_cart و tests لە خۆ دەگرێت. bazaar_cart: __init__.py (بوخچەکە دەکاتە پاکێج)، models.py (Product، OrderLine، Order: دراوەی هاوبەش)، repository.py (چینی دەستڕاگەیشتن بە دراوە: SQL)، services.py (چینی لۆجیکی کاروبار: R1 تا R4)، cli.py (چینی نێوبەستەی بەکارهێنەر: دەپرسێت و چاپ دەکات) و receipt.py (چینی نێوبەستەی بەکارهێنەر 2: پسوولەی JSON). tests: __init__.pyـی بەتاڵ (unittest حاڵەتەکانی تاقیکردنەوە دەدۆزێتەوە) و test_services.py (یاساکان بە تەنیا تاقی دەکاتەوە).
One file per jobبۆ هەر کارێک یەک CêIT

models.py holds the plain data that every layer passes around. It has no input, no SQL and no rules (classes and objects explains class; @dataclass writes __init__ for you):

models.py ئەو سادەیە لە خۆ دەگرێت کە هەموو دەیگوازنەوە. نە ، نە و نە یاسای تێدایە (کلاسەکان و ئۆبجێکتەکان class ڕوون دەکاتەوە؛ @dataclass خۆی __init__ت بۆ دەنووسێت):

The shared data shapesشێوە هاوبەشەکانی دراوەbazaar_cart/models.pyPYTHON
"""The data that every layer talks about. No input, no SQL, no rules."""
from dataclasses import dataclass


@dataclass
class Product:
    code: str
    name: str
    price_per_kg: int  # IQD (made-up prices)
    stock_g: int       # grams in the storeroom


@dataclass
class OrderLine:
    name: str
    grams: int
    price: int


@dataclass
class Order:
    lines: list[OrderLine]
    goods_total: int
    delivery: int

    @property
    def total(self):
        return self.goods_total + self.delivery
Explain the codeڕوونکردنەوەی کۆدەکە
Lineدێڕی 26-28

total is computed when you read it, so it can never disagree with the two numbers.

total کاتی خوێندنەوە دەژمێردرێت، بۆیە هەرگیز لەگەڵ دوو ژمارەکەدا ناکۆک نابێت.

Step 1: the data access layerهەنگاوی 1: چینی دەستڕاگەیشتن بە دراوە

The data access layer is the only place that knows how products are stored. Here it is a repository class: other code asks it for a product by code and gets a Product object back, never a row or a cursor. It uses sqlite3 with ? placeholders, as in SQLite basics.

تاکە شوێنە کە دەزانێت بەرهەمەکان چۆن هەڵدەگیرێن. لێرەدا : کۆدی تر بە کۆدی بەرهەم داوای بەرهەمێکی لێ دەکات و Product وەردەگرێتەوە، هەرگیز ڕیز یان cursor نا. sqlite3 بە شوێنگری ? بەکار دەهێنێت، وەک لە بنەماکانی SQLite.

ProductRepository: rows in, objects outProductRepository: ڕیز دێتە ژوورەوە، ئۆبجێکت دەچێتە دەرەوەbazaar_cart/repository.pyPYTHON
"""Data access layer: the only file that knows SQL."""
import sqlite3

from .models import Product


class ProductRepository:
    def __init__(self, db):
        self.db = db

    def get(self, code):
        row = self.db.execute(
            "SELECT code, name, price_per_kg, stock_g FROM products WHERE code = ?",
            (code,)).fetchone()
        return Product(*row) if row else None

    def reduce_stock(self, code, grams):
        self.db.execute(
            "UPDATE products SET stock_g = stock_g - ? WHERE code = ?", (grams, code))
        self.db.commit()


def open_demo_db():
    """Soran's five products in a database in memory (made-up numbers)."""
    db = sqlite3.connect(":memory:")
    db.execute("CREATE TABLE products (code TEXT PRIMARY KEY, name TEXT,"
               " price_per_kg INTEGER, stock_g INTEGER)")
    db.executemany("INSERT INTO products VALUES (?, ?, ?, ?)", [
        ("walnuts", "Walnuts", 18000, 20000), ("sumac", "Sumac", 8000, 5000),
        ("figs", "Dried figs", 12000, 8000), ("tea", "Black tea", 14000, 10000),
        ("honey", "Honey", 30000, 3000)])
    return db
Explain the codeڕوونکردنەوەی کۆدەکە
Lineدێڕی 11-15

get() returns a Product, or None when the code does not exist. Deciding what «not found» means is left to the rules.

get() Product دەگەڕێنێتەوە، یان None کاتێک کۆدەکە نییە. بڕیاردان لەسەر ئەوەی «نەدۆزرایەوە» چی دەگەیەنێت بۆ یاساکان جێدەهێڵرێت.

Lineدێڕی 23-32

The demo database with made-up stock in grams. A real shop would open a file or a server here, and only this file would change.

نموونە بە بڕی کۆگای دروستکراو بە گرام. دوکانێکی ڕاستەقینە لێرەدا یان دەکاتەوە و تەنیا ئەم دەگۆڕێت.

Left: a products table in a database with rows walnuts 18000, sumac 8000, honey 30000. Middle: the ProductRepository box with get(code) and reduce_stock(code, grams), the only class that knows SQL; arrows labelled SELECT and UPDATE go to the table and rows come back. Right: a Product object, Product(code='sumac', name='Sumac', price_per_kg=8000, stock_g=5000). Bottom: upper layers see Product objects, never rows or SQL.چەپ: خشتەی products لە بنکەدراوەدا بە ڕیزەکانی walnuts 18000، sumac 8000، honey 30000. ناوەڕاست: چوارگۆشەی ProductRepository بە get(code) و reduce_stock(code, grams)، تاکە کلاس کە SQL دەزانێت؛ تیرەکانی SELECT و UPDATE دەچن بۆ خشتەکە و ڕیزەکان دەگەڕێنەوە. ڕاست: ئۆبجێکتێکی Product، واتە Product(code='sumac', name='Sumac', price_per_kg=8000, stock_g=5000). خوارەوە: چینەکانی سەرەوە تەنیا ئۆبجێکتەکانی Product دەبینن، نە ڕیز و نە SQL.
Rows stay in the data layerڕیزەکان لە دەمێننەوەCêIT

A hand-written repository is one way to build this layer. An ORM library can write the SQL for you; object–relational mapping in PHP shows Eloquent and Doctrine. Either way, the layers above see objects only.

دەستنووس یەک ڕێگەیە بۆ دروستکردنی ئەم . دەتوانێت بۆت بنووسێت؛ ORM لە PHPـدا Eloquent و Doctrine پیشان دەدات. بە هەر ڕێگەیەک بێت، سەرەوە تەنیا دەبینن.

Step 2: the business logic layerهەنگاوی 2: چینی لۆجیکی کاروبار

Now every rule moves into one file. OrderService receives a repository when it is created; it does not care whether the repository uses SQLite or something else. (Handing an object what it needs from outside has a name, dependency injection, the topic of the next-but-one lesson.) A rule break raises a ValueError with a clear message, as in raising your own exceptions.

ئێستا هەموو یاسایەک دەچێتە یەک . OrderService کاتی دروستبوون وەردەگرێت؛ گرنگ نییە بۆی کە ئەو SQLite بەکار دەهێنێت یان شتێکی تر. (دانی ئەوەی پێویستیەتی لە دەرەوە ناوی هەیە: تێخستنی وابەستەیی، بابەتی دوو وانە دواتر.) کاتێک یاسایەک دەشکێت، ValueError بە پەیامێکی ڕوونەوە هەڵدەدرێت، وەک لە هەڵدانی هەڵاوێردەکانی خۆت.

OrderService: R1–R4 in one place⁦OrderService: R1⁩ تا ⁦R4⁩ لە یەک شوێنداbazaar_cart/services.pyPYTHON
"""Business logic layer: Soran's rules R1-R4 live here, and only here."""
import math

from .models import Order, OrderLine

MIN_GRAMS, MAX_GRAMS = 100, 10_000  # R2
DELIVERY_FEE = 3_000                # R4: inside Sulaimani, IQD
FREE_DELIVERY_FROM = 50_000         # R4: goods total for free delivery


def round_up_250(amount):
    """R1: round UP to the next 250 IQD."""
    return math.ceil(amount / 250) * 250


def line_price(price_per_kg, grams):
    """R1: the price of one line."""
    return round_up_250(price_per_kg * grams / 1000)


def delivery_fee(goods_total):
    """R4: 3,000 IQD, free from 50,000 IQD of goods."""
    return 0 if goods_total >= FREE_DELIVERY_FROM else DELIVERY_FEE


class OrderService:
    def __init__(self, repo):
        self.repo = repo  # any object with get() and reduce_stock()

    def price_order(self, items):
        """Check every line (R2, R3) and price the order (R1, R4). Saves nothing."""
        lines = []
        for code, grams in items:
            product = self.repo.get(code)
            if product is None:
                raise ValueError(f"unknown product: {code}")
            if not MIN_GRAMS <= grams <= MAX_GRAMS:
                raise ValueError(f"{product.name}: {grams} g is outside 100-10,000 g")
            if grams > product.stock_g:
                raise ValueError(f"{product.name}: only {product.stock_g:,} g in stock")
            lines.append(OrderLine(product.name, grams,
                                   line_price(product.price_per_kg, grams)))
        goods = sum(line.price for line in lines)
        return Order(lines, goods, delivery_fee(goods))

    def place_order(self, items):
        """Price the order, take the goods from stock, return the total in IQD."""
        order = self.price_order(items)
        for code, grams in items:
            self.repo.reduce_stock(code, grams)
        return order.total
Explain the codeڕوونکردنەوەی کۆدەکە
Lineدێڕی 6-8

The numbers of R2 and R4 are named constants, written once.

ژمارەکانی ⁦R2⁩ و ⁦R4⁩ ناودارن و تەنیا یەک جار نووسراون.

Lineدێڕی 11-23

Small pure functions: easy to read and to test one by one.

بچووک و ڕوون: خوێندنەوە و تاقیکردنەوەیان یەک بە یەک ئاسانە.

Lineدێڕی 30-44

price_order() checks R2 and R3 for every line and prices the order. It saves nothing, so a screen can show the bill first.

price_order() بۆ هەر دێڕێک ⁦R2⁩ و ⁦R3⁩ دەپشکنێت و نرخی داواکارییەکە دادەنێت. هیچ پاشەکەوت ناکات، بۆیە شاشە دەتوانێت سەرەتا پسوولەکە پیشان بدات.

Lineدێڕی 46-51

place_order() reuses price_order(), then takes the goods from stock through the repository.

place_order() دووپاتە price_order() بەکار دەهێنێتەوە، پاشان لە ڕێی کاڵاکان لە کۆگا کەم دەکاتەوە.

Five rule cards on the left point into services.py on the right. R1, round up to the next 250 IQD, points to round_up_250(amount) and line_price(price_per_kg, grams). R2, each line 100 g to 10,000 g, points to MIN_GRAMS <= grams <= MAX_GRAMS. R3, never more than the stock, points to grams > product.stock_g. R4, delivery 3,000 IQD, free from 50,000, points to delivery_fee(goods_total). R5, later in 1.1.0, 5 % off from 100,000, points to a dashed empty slot: a discount will be added here.پێنج کارتی یاسا لە چەپەوە ئاماژە بۆ services.py دەکەن لە ڕاستەوە. R1، نزیککردنەوە بۆ سەرەوە بۆ 250 دیناری دواتر، ئاماژە بۆ round_up_250(amount) و line_price(price_per_kg, grams) دەکات. R2، هەر دێڕێک 100 تا 10,000 گرام، بۆ MIN_GRAMS <= grams <= MAX_GRAMS. R3، هەرگیز زیاتر لە بڕی کۆگا نا، بۆ grams > product.stock_g. R4، گەیاندن 3,000 دینار و لە 50,000ـەوە بێبەرانبەر، بۆ delivery_fee(goods_total). R5، دواتر لە 1.1.0، داشکاندنی 5 لە سەد لە 100,000ـەوە، ئاماژە بۆ شوێنێکی بەتاڵی خاڵخاڵ دەکات: داشکاندنەکە لێرە زیاد دەکرێت.
Each rule lives in exactly one placeهەر یاسایەک ڕێک لە یەک شوێندا دەژیCêIT

Step 3: the presentation layerهەنگاوی 3: چینی نێوبەستەی بەکارهێنەر

The command-line UI now only asks, shows and passes things on. It checks one thing itself, that the grams are a whole number, so it can ask again politely. Every real rule comes from the service, and every refusal arrives as a ValueError message.

، کە لێرەدا ، ئێستا تەنیا دەپرسێت، پیشان دەدات و شتەکان دەگەیەنێتە شوێنی خۆیان. تەنیا یەک شت خۆی دەپشکنێت: ئایا گرامەکان ، بۆ ئەوەی بە ڕێزەوە دووپاتە بپرسێت. هەموو یاسایەکی ڕاستەقینە لە دێت و هەموو ڕەتکردنەوەیەک وەک پەیامی ValueError دەگات.

cli.py: ask, show, pass on⁦cli.py⁩: بپرسە، پیشان بدە، بیگەیەنەbazaar_cart/cli.pyPYTHONType in a terminalلە تێرمیناڵدا بینووسە
"""Presentation layer: asks and shows. No rules, no SQL."""
from .repository import ProductRepository, open_demo_db
from .services import OrderService


def ask_items():
    items = []
    while True:
        code = input("Product code (empty to finish): ").strip()
        if not code:
            return items
        text = input("Grams: ").strip()
        if not text.isdigit():
            print("  Please type the grams as a whole number.")
            continue
        items.append((code, int(text)))


def show_bill(order):
    for line in order.lines:
        print(f"  {line.name:<10} {line.grams:>5} g {line.price:>7,} IQD")
    print(f"  Goods {order.goods_total:,} + delivery {order.delivery:,}"
          f" = {order.total:,} IQD")


def main():
    service = OrderService(ProductRepository(open_demo_db()))
    items = ask_items()
    try:
        show_bill(service.price_order(items))
        if input("Confirm (y/n)? ").strip().lower() == "y":
            total = service.place_order(items)
            print(f"Order placed: {total:,} IQD. Thank you!")
    except ValueError as error:
        print("Sorry,", error)


if __name__ == "__main__":
    main()
Explain the codeڕوونکردنەوەی کۆدەکە
Lineدێڕی 12-15

A format check for friendliness only. «1.5 kg» is not refused by a rule; the screen just asks again.

پشکنینی شێوە تەنیا بۆ دۆستایەتی. «⁦1.5 kg⁩» لەلایەن یاسایەکەوە ڕەت ناکرێتەوە؛ شاشەکە تەنیا دووپاتە دەپرسێت.

Lineدێڕی 26-35

main() connects the three layers once. The except shows the business layer's message to the customer.

main() سێ یەک جار بە یەکەوە دەبەستێت. except پەیامی بە کڕیار پیشان دەدات.

Follow one order through the layers: the UI reads «sumac 350», the business layer asks the repository for sumac, the rules run, and an Order travels back up to be printed.

یەک داواکاری بە ناو بەدوادا بچۆ: «⁦sumac 350⁩» دەخوێنێتەوە، لە داوای سوماق دەکات، یاساکان جێبەجێ دەبن و Orderـێک دەگەڕێتەوە سەرەوە بۆ چاپکردن.

An animation in six frames over three stacked layers and an SQLite database. Frame 1: the UI layer reads Product code: sumac, Grams: 350. Frame 2: an arrow goes down to the business logic layer with service.price_order([("sumac", 350)]). Frame 3: the business layer asks the repository, which sends SELECT … WHERE code = ? to the database. Frame 4: the repository returns Product(name="Sumac", price_per_kg=8000, stock_g=5000). Frame 5: the rules run only in the business layer: R2 350 g OK, R3 350 ≤ 5000 OK, R1 2,800 → 3,000, R4 plus 3,000 delivery. Frame 6: an Order comes back up and the UI prints Goods 3,000 + delivery 3,000 = 6,000 IQD.
An animation in six frames over three stacked layers and an SQLite database. Frame 1: the UI layer reads Product code: sumac, Grams: 350. Frame 2: an arrow goes down to the business logic layer with service.price_order([("sumac", 350)]). Frame 3: the business layer asks the repository, which sends SELECT … WHERE code = ? to the database. Frame 4: the repository returns Product(name="Sumac", price_per_kg=8000, stock_g=5000). Frame 5: the rules run only in the business layer: R2 350 g OK, R3 350 ≤ 5000 OK, R1 2,800 → 3,000, R4 plus 3,000 delivery. Frame 6: an Order comes back up and the UI prints Goods 3,000 + delivery 3,000 = 6,000 IQD.
One order, down the layers and back upیەک داواکاری، بە ناو بۆ خوارەوە و گەڕانەوەCêIT

Step 3: the presentation layerهەنگاوی 3: چینی نێوبەستەی بەکارهێنەر

A second presentation, the same rulesچینی نێوبەستەی بەکارهێنەرێکی دووەم، هەمان یاساکان

Soran's nephew builds a website that needs the bill as JSON. Karwan adds a second presentation in a few lines. It reuses the same service, so every rule, R1 to R4, is already there:

برازای سۆران دروست دەکات کە پسوولەکەی وەک پێویستە. کاروان بە چەند دێڕێک دووەم زیاد دەکات. هەمان دووپاتە بەکار دەهێنێتەوە، بۆیە هەموو یاساکان، ⁦R1⁩ تا ⁦R4⁩، لە پێشدا ئامادەن:

receipt.py: the bill as JSON⁦receipt.py⁩: پسوولە وەک JSONbazaar_cart/receipt.pyPYTHONType in a terminalلە تێرمیناڵدا بینووسە
"""A second presentation on the same business layer: a JSON receipt for a website or an app."""
import json

from .repository import ProductRepository, open_demo_db
from .services import OrderService


def receipt_json(service, items):
    order = service.price_order(items)
    return json.dumps({
        "lines": [{"product": line.name, "grams": line.grams, "price": line.price}
                  for line in order.lines],
        "goods_total": order.goods_total,
        "delivery": order.delivery,
        "total": order.total,
    }, indent=2)


if __name__ == "__main__":
    service = OrderService(ProductRepository(open_demo_db()))
    print(receipt_json(service, [("honey", 1000), ("tea", 1500)]))
Two blue presentation boxes at the top: cli.py shows a terminal bill (Sumac 350 g 3,000 IQD, Walnuts 1500 g 27,000 IQD, Goods 30,000 + delivery 3,000 = 33,000 IQD) and receipt.py shows JSON (lines, goods_total 51000, delivery 0, total 51000). Both arrows go into one green OrderService box with price_order() and place_order(), which calls ProductRepository below. A dashed box says: later, a phone app or a Telegram bot. Bottom: a new screen reuses every rule; nothing is copied.دوو چوارگۆشەی شینی پیشاندان لە سەرەوە: cli.py پسوولەیەکی تێرمیناڵ پیشان دەدات (Sumac 350 g 3,000 IQD، Walnuts 1500 g 27,000 IQD، Goods 30,000 + delivery 3,000 = 33,000 IQD) و receipt.py JSON پیشان دەدات (lines، goods_total 51000، delivery 0، total 51000). هەردوو تیرەکە دەچنە ناو یەک چوارگۆشەی سەوزی OrderService بە price_order() و place_order()، کە ProductRepository لە خوارەوە بانگ دەکات. چوارگۆشەیەکی خاڵخاڵ دەڵێت: دواتر ئەپێکی مۆبایل یان بۆتێکی تێلێگرام. خوارەوە: هەر شاشەیەکی نوێ هەموو یاساکان دووپاتە بەکار دەهێنێتەوە؛ هیچ شتێک کۆپی ناکرێت.
Many screens can share one business layerچەندین شاشە دەتوانن یەک بەکار بهێننCêIT

Testing the rules without a screen or a databaseتاقیکردنەوەی یاساکان بێ شاشە و بێ بنکەدراوە

Dilan can finally test R1–R4 directly. In the test she gives the service a fake repository, a dictionary in memory, instead of SQLite. The service only calls get() and reduce_stock(), so it cannot tell the difference. No typing, no database, and the tests run in a moment. (Automated testing teaches unittest and test doubles in full.)

دیلان ئێستا دەتوانێت ⁦R1⁩ تا ⁦R4⁩ ڕاستەوخۆ تاقی بکاتەوە. لە ساختە دەداتە ، لە بیرگەدا، لە جیاتی SQLite. تەنیا get() و reduce_stock() بانگ دەکات، بۆیە جیاوازییەکە نازانێت. نە تایپکردن، نە و لە چاوتروکانێکدا تەواو دەبن. (تاقیکردنەوەی خۆکار unittest و بە تەواوی فێر دەکات.)

Six tests for the business layerشەش حاڵەتی تاقیکردنەوە بۆ چینی لۆجیکی کاروبارtests/test_services.pyPYTHONType in a terminalلە تێرمیناڵدا بینووسە
import unittest

from bazaar_cart.models import Product
from bazaar_cart.services import OrderService


class InMemoryProductRepository:
    """A fake data layer: a dictionary instead of a database."""

    def __init__(self, products):
        self.products = {p.code: p for p in products}

    def get(self, code):
        return self.products.get(code)

    def reduce_stock(self, code, grams):
        self.products[code].stock_g -= grams


class OrderServiceTest(unittest.TestCase):
    def setUp(self):
        self.repo = InMemoryProductRepository([
            Product("sumac", "Sumac", 8000, 5000),
            Product("honey", "Honey", 30000, 3000)])
        self.service = OrderService(self.repo)

    def test_r1_line_price_rounds_up_to_250(self):
        order = self.service.price_order([("sumac", 350)])
        self.assertEqual(order.lines[0].price, 3000)

    def test_r2_less_than_100_g_is_refused(self):
        with self.assertRaises(ValueError):
            self.service.price_order([("sumac", 50)])

    def test_r3_not_more_than_the_stock(self):
        with self.assertRaises(ValueError):
            self.service.price_order([("honey", 3500)])

    def test_r4_delivery_costs_3000_below_50000(self):
        order = self.service.price_order([("sumac", 350)])
        self.assertEqual(order.delivery, 3000)

    def test_r4_free_delivery_from_50000(self):
        order = self.service.price_order([("honey", 1700)])  # 51,000 IQD
        self.assertEqual(order.delivery, 0)

    def test_place_order_takes_goods_from_stock(self):
        self.service.place_order([("honey", 1000)])
        self.assertEqual(self.repo.products["honey"].stock_g, 2000)


if __name__ == "__main__":
    unittest.main()
Explain the codeڕوونکردنەوەی کۆدەکە
Lineدێڕی 7-17

The fake data layer: the same two methods as ProductRepository, backed by a dictionary.

ساختە: هەمان دوو ProductRepository، بە لە پشتیەوە.

Lineدێڕی 21-25

setUp() builds a fresh service before every test.

setUp() پێش هەر نوێ دروست دەکات.

OrderService(repo) at the top uses repo.get() and repo.reduce_stock(). A solid arrow goes to ProductRepository (real program, SQLite database) and a dashed arrow to InMemoryProductRepository (in the tests, a dictionary). Both have get(code) and reduce_stock(code, grams). Bottom: the same methods, so the service cannot tell the difference.OrderService(repo) لە سەرەوە get و reduce_stock بەکار دەهێنێت. تیرێکی پڕ دەچێت بۆ ProductRepository (بەرنامەی ڕاستەقینە، بنکەدراوەی SQLite) و تیرێکی خاڵخاڵ بۆ InMemoryProductRepository (لە حاڵەتەکانی تاقیکردنەوەدا، فەرهەنگێک). هەردووکیان get(code) و reduce_stock(code, grams)ـیان هەیە. خوارەوە: میتۆدەکان هەمان شتن، بۆیە خزمەتگوزارییەکە جیاوازییەکە نازانێت.
A fake data layer makes the rules easy to test ساختە تاقیکردنەوەی یاساکان ئاسان دەکاتCêIT

Validation: a friendly check and a strict checkپشتڕاستکردنەوە: پشکنینێکی دۆستانە و پشکنینێکی توند

Where should «grams must be 100 to 10,000» be checked? In both places, for different reasons. The UI checks the format to be friendly: «1.5 kg» is not a number, ask again. The business layer enforces the rules always, because tomorrow a different screen may call it with no checks at all. This is the same idea as client-side and server-side validation in PHP: never trust what arrives from a screen.

«گرام دەبێت ⁦100⁩ تا ⁦10,000⁩ بێت» لە کوێ بپشکنرێت؟ لە هەردوو شوێن، بە دوو هۆی جیاواز. شێوەکە دەپشکنێت بۆ دۆستایەتی: «⁦1.5 kg⁩» ژمارە نییە، دووپاتە بپرسە. هەمیشە یاساکان جێبەجێ دەکات، چونکە سبەی لەوانەیە شاشەیەکی تر بێ هیچ پشکنینێک بانگی بکات. ئەمە هەمان بیرۆکەی پشتڕاستکردنەوەی لای ڕاژەخواز و پشتڕاستکردنەوەی لای ڕاژەکارـە لە PHPـدا: هەرگیز متمانە بەوە مەکە کە لە شاشەیەکەوە دێت.

A customer types "1.5 kg" or "-200". Gate 1, the UI layer: is it a whole number? A friendly message, ask again. Gate 2, the business logic layer: R2 100 to 10,000 g? R3 in stock? Always, for every screen. A careless web page sends -200 g straight to gate 2 and is refused: -200 g is outside 100 to 10,000 g. Bottom: the UI check is for friendliness; the business check is for safety.بەکارهێنەر دەنووسێت "1.5 kg" یان "-200". دەروازەی 1، چینی نێوبەستەی بەکارهێنەر: ژمارەی تەواوە؟ پەیامی دۆستانە، دووپاتە بپرسە. دەروازەی 2، چینی لۆجیکی کاروبار: R2 100 تا 10,000 گرام؟ R3 لە کۆگادا هەیە؟ هەمیشە، بۆ هەموو شاشەیەک. لاپەڕەتۆڕێکی بێئاگا -200 گرام ڕاستەوخۆ دەنێرێت بۆ دەروازەی 2 و ڕەت دەکرێتەوە: -200 گرام لە دەرەوەی 100 تا 10,000ـە. خوارەوە: پشکنینی چینی نێوبەستەی بەکارهێنەر بۆ دۆستایەتییە؛ پشکنینی چینی لۆجیکی کاروبار بۆ سەلامەتییە.
Two gates with two different jobsدوو دەروازە بە دوو ئەرکی جیاوازCêIT
A screen with no checks still cannot break R2شاشەیەک بێ پشکنین هێشتا ناتوانێت ⁦R2⁩ بشکێنێتcareless_page.pyPYTHONType in a terminalلە تێرمیناڵدا بینووسە
# A careless web page sends whatever the visitor typed, with no checks.
from bazaar_cart.repository import ProductRepository, open_demo_db
from bazaar_cart.services import OrderService

service = OrderService(ProductRepository(open_demo_db()))
for grams in [-200, 350, 25000]:
    try:
        order = service.price_order([("sumac", grams)])
        print(grams, "g -> accepted, total", order.total)
    except ValueError as error:
        print(grams, "g -> refused:", error)

Layers in websites and mobile appsچینەکان لە تۆڕگەکان و ئەپی مۆبایلدا

The same three jobs appear everywhere, under different names. Microsoft's guide notes that a new ASP.NET Core project starts as one «all-in-one» project and suggests separate layers as it grows. Android's guide asks for at least a UI layer and a data layer, with an optional domain layer of use cases for complex or shared business logic; it places business logic in the data layer's repositories or in the domain layer, never in the screens.

هەمان سێ کار لە هەموو شوێنێکدا هەن، بە ناوی جیاواز. ڕێنمایی Microsoft دەڵێت پڕۆژەیەکی نوێی ⁦ASP.NET Core⁩ وەک یەک پڕۆژەی «هەموو شت لە یەکدا» دەست پێ دەکات و پێشنیار دەکات کە بە گەورەبوونی جیا هەبێت. ڕێنمایی Android لانیکەم و چینی دەوێت، لەگەڵ چینی domainـی ئارەزوومەندانە کە use caseی تێدایە بۆ ئاڵۆز یان هاوبەش؛ لە چینی یان لە چینی domainـدا دادەنێت، هەرگیز لە شاشەکاندا نا.

Three columns with the same three coloured layers. Bazaar Cart (Python): cli.py and receipt.py; services.py with OrderService; repository.py. Android guide: screens and ViewModels; the optional domain layer with use cases; the data layer with repositories and the business rules. A clean PHP site: order.php and templates; OrderService.php; ProductRepository.php with PDO. Bottom: Android keeps rules in the data layer or a domain layer, never in the screens.سێ ستوون بە هەمان سێ چینی ڕەنگاوڕەنگ. Bazaar Cart (Python): cli.py و receipt.py؛ services.py بە OrderService؛ repository.py. ڕێنمایی Android: شاشەکان و ViewModel؛ چینی domainـی ئارەزوومەندانە بە use case؛ چینی دراوە بە کۆگاکانی دراوە و یاساکانی کار. تۆڕگەیەکی ڕێکخراوی PHP: order.php و قاڵبەکان؛ OrderService.php؛ ProductRepository.php بە PDO. خوارەوە: Android یاساکان لە چینی دراوە یان چینی domainـدا دادەنێت، هەرگیز لە شاشەکاندا نا.
The same three jobs, different namesهەمان سێ کار، بە ناوی جیاوازCêIT
  • PHP: the pages of the CRUD application mix HTML, rules and SQL in each file. Moving the SQL into one ProductRepository.php and the rules into one OrderService.php is the same refactoring you did here.
  • Mobile: in MVVM for mobile apps the view and the view model form the UI layer, and services and repositories form the data layer.
  • PHP: لاپەڕەکانی بەرنامەی CRUD لە هەر ، یاسا و تێکەڵ دەکەن. گواستنەوەی بۆ یەک ProductRepository.php و یاساکان بۆ یەک OrderService.php هەمان ئەو کە لێرەدا کردت.
  • مۆبایل: لە MVVM بۆ ئەپی مۆبایل و پێک دەهێنن و و چینی پێک دەهێنن.

One rule, one place: DRYیەک یاسا، یەک شوێن: DRY

Karwan's first web page did not ask the service for the delivery fee. He typed the rule again, from memory. Watch what happens with goods of exactly 50,000 IQD (walnuts 2,000 g + black tea 1,000 g):

یەکەم کاروان کرێی گەیاندنی لە نەپرسی. یاساکەی لە بیرەوە دووپاتە نووسییەوە. سەیر بکە چی ڕوو دەدات بە کاڵای ڕێک ⁦50,000⁩ دینار (⁦2,000⁩ گرام گوێز + ⁦1,000⁩ گرام چای ڕەش):

Common mistake: a copied ruleهەڵەی باو: یاسای کۆپیکراوcopied_rule.pyPYTHONType in a terminalلە تێرمیناڵدا بینووسە
# Karwan's web page copied the delivery rule instead of asking the business layer.
from bazaar_cart.repository import ProductRepository, open_demo_db
from bazaar_cart.services import OrderService

service = OrderService(ProductRepository(open_demo_db()))
order = service.price_order([("walnuts", 2000), ("tea", 1000)])
print("Goods total:   ", order.goods_total)

print("Business layer:", order.delivery)            # the one real rule (R4)
copied = 0 if order.goods_total > 50000 else 3000   # a copy, typed again
print("Copied rule:   ", copied)
A goods total of 50,000 IQD goes to two boxes. The business layer (the rule) has goods >= 50000 → 0 and gives delivery 0 IQD. The web page (a copy) has goods > 50000 → 0 and gives delivery 3,000 IQD, marked with a cross. Fix: the page asks the business layer and shows order.delivery. One rule, one place: DRY.کۆی کاڵای 50,000 دینار دەچێت بۆ دوو چوارگۆشە. چینی لۆجیکی کاروبار (یاساکە) مەرجی goods >= 50000 بەکار دەهێنێت و گەیاندن دەکاتە 0 دینار. لاپەڕەتۆڕ (کۆپییەک) مەرجی goods > 50000 بەکار دەهێنێت و گەیاندن دەکاتە 3,000 دینار، بە نیشانەی ×. چارەسەر: لاپەڕەتۆڕەکە لە چینی لۆجیکی کاروبار دەپرسێت و order.delivery پیشان دەدات. یەک یاسا، یەک شوێن: DRY.
Two copies of R4 give two answersدوو کۆپیی ⁦R4⁩ دوو وەڵام دەدەنCêIT
Definitionپێناسە

"Don't repeat yourself": every rule or piece of knowledge should live in one place in the code, so that a change is made once and cannot be forgotten in a copy.

«دووپاتە مەکەرەوە»: هەر یاسایەک یان زانیارییەک دەبێت لە یەک شوێنی کۆددا بژی، بۆ ئەوەی گۆڕانکاری یەک جار بکرێت و لە کۆپییەکدا لەبیر نەچێت.

The fix is one line: show order.delivery, which the business layer already calculated. The DRY principle («don't repeat yourself», named by Andy Hunt and Dave Thomas in The Pragmatic Programmer, 1999) says every rule should have one home. Microsoft's guide lists it as a reason for layers: shared logic is written once.

چارەسەرەکە یەک دێڕە: order.delivery پیشان بدە، کە پێشتر ژماردوویەتی. (DRY، کورتکراوەی «⁦don't repeat yourself⁩»، کە Andy Hunt و Dave Thomas لە ⁦1999⁩ لە The Pragmatic Programmerـدا ناویان لێنا) دەڵێت هەر یاسایەک دەبێت یەک ماڵی هەبێت. ڕێنمایی Microsoft ئەمە وەک هۆیەک بۆ دادەنێت: لۆجیکی هاوبەش یەک جار دەنووسرێت.

Why the split works: coupling and cohesionبۆچی دابەشکردنەکە کار دەکات: پێکەوەبەستراوی و یەکگرتوویی

Requirements and design introduced two words for judging a design. Cohesion: how well the things in one part belong together. Coupling: how much one part depends on another part's insides. Good designs aim for high cohesion and low coupling. Compare before and after:

داواکاری و دیزاین دوو وشەی ناساند بۆ هەڵسەنگاندنی دیزاینێک. : شتەکانی یەک بەش تا چەند پێکەوە دەگونجێن. : بەشێک تا چەند پشت بە ناوەوەی بەشێکی تر دەبەستێت. دیزاینی باش زۆر و کەمی دەوێت. پێش و دوای بەراورد بکە:

Questionپرسیارall_in_one.pyall_in_one.pyThe layered package
What does one file do?یەک چی دەکات؟Everything (low cohesion)هەموو شت ( کەم)One job each (high cohesion)هەریەکە یەک کار ( زۆر)
What do the rules know about SQLite?یاساکان چی لەبارەی SQLite دەزانن؟Its rows and columns (high coupling)ڕیز و ستوونەکانی ( زۆر)Only get() and reduce_stock() (low coupling)تەنیا get() و reduce_stock() ( کەم)
A new screen needs…شاشەیەکی نوێ پێویستی بە…a copy of the rulesکۆپییەکی یاساکانone call to the serviceیەک بانگکردنی
Testing R4 needs…تاقیکردنەوەی ⁦R4⁩ پێویستی بە…a person typingکەسێک کە تایپ بکاتa fake repository ساختە

Fowler gives the main reason for layering in one phrase: it reduces the scope of your attention. While you change the delivery rule, you can forget about screens and SQL. That is what makes software more maintainable.

Fowler هۆی سەرەکیی چینبەندی لە یەک ڕستەدا دەڵێت: سنووری سەرنجت بچووک دەکاتەوە. کاتێک یاسای گەیاندن دەگۆڕیت، دەتوانیت شاشە و لەبیر بکەیت. ئەمە ئەوەیە کە زیاد دەکات.

Extra: business rules at the centreزیادە: یاساکانی کار لە ناوەڕاستدا

In our layers the business layer still knows that a repository exists below it. Many teams go one step further. In clean architecture (also called hexagonal, ports-and-adapters or onion architecture), the business rules sit at the centre and define the interfaces they need; the database code and the screens are outer details that depend on the centre. Arrows point inward.

لە ئێمەدا هێشتا دەزانێت لە ژێریەوە هەیە. زۆر تیم هەنگاوێکی تر دەڕۆن. لە تەلارسازیی Cleanـدا (کە پێی دەوترێت hexagonal، ⁦ports-and-adapters⁩ یان onion)، یاساکانی کار لە ناوەڕاستدان و پێویستی خۆیان دیاری دەکەن؛ کۆدی و شاشەکان وردەکاریی دەرەوەن و پشت بە ناوەڕاست دەبەستن. تیرەکان بەرەو ناوەوەن.

Three rings. Centre: rules (R1 to R5, Order). Middle ring: services (OrderService). Outer ring: details, with CLI, SQLite and SMS boxes whose arrows point inward. Text: arrows point inward; details depend on the rules, never the other way. Change the database or the screen: the rules in the middle stay the same.سێ بازنە. ناوەڕاست: یاساکان (R1 تا R5، Order). بازنەی ناوەڕاست: خزمەتگوزارییەکان (OrderService). بازنەی دەرەوە: وردەکارییەکان، بە چوارگۆشەکانی CLI، SQLite و SMS کە تیرەکانیان بەرەو ناوەوەن. دەق: تیرەکان بەرەو ناوەوەن؛ وردەکارییەکان پشت بە یاساکان دەبەستن، هەرگیز بە پێچەوانەوە نا. بنکەدراوە یان شاشە بگۆڕە: یاساکانی ناوەڕاست وەک خۆیان دەمێننەوە.
Clean architecture: details point inwardتەلارسازیی Clean: وردەکارییەکان بەرەو ناوەوە ئاماژە دەکەنCêIT

When layers are too muchکەی چینەکان زیادن

Layers cost files and a little ceremony. For a ten-line script you run once, they are not worth it. Bazaar Cart needed them because it has several screens, a database, a team of four and rules that change. Fowler adds a second warning for large systems: do not make «presentation», «domain» and «data» the top-level folders of a huge program. Split it first by feature (orders, products, delivery), and layer each feature inside.

و هەندێک ڕێوڕەسمیان دەوێت. بۆ دە دێڕی کە یەک جار کاری پێ دەکەیت، شایەنی نین. Bazaar Cart پێویستی پێیان بوو چونکە چەند شاشەیەک، ، تیمێکی چوار کەسی و یاسای گۆڕاوی هەیە. Fowler ئاگادارکردنەوەیەکی دووەم بۆ گەورە زیاد دەکات: «پیشاندان»، «domain» و «» مەکە بە بوخچە سەرەکییەکانی زۆر گەورە. سەرەتا بەپێی بەش دابەشی بکە (داواکاری، بەرهەم، گەیاندن) و پاشان هەر بەشێک لە ناوەوە چینبەند بکە.

Three grey feature boxes: orders/, products/ and delivery/. Each holds its own user interface layer, business logic layer and data access layer. Bottom: each team owns a feature with its own three layers (Fowler, 2015).سێ چوارگۆشەی خۆڵەمێشی بۆ بەشەکان: orders/، products/ و delivery/. هەریەکەیان چینی نێوبەستەی بەکارهێنەر، چینی لۆجیکی کاروبار و چینی دەستڕاگەیشتن بە دراوەی خۆی هەیە. خوارەوە: هەر تیمێک بەشێکی هەیە بە سێ چینی خۆیەوە (Fowler، 2015).
Large systems: features first, then layers گەورە: سەرەتا بەش، پاشان CêIT
  • Tiny script or a one-off tool: one file is fine; split it the day a second screen or a test is needed.
  • An app like Bazaar Cart: three layers in one package.
  • A large system with many teams: features first, layers inside each feature. Fowler also warns against teams split by layer: «developers don't have to be full-stack but teams should be».
  • بچووک یان ئامرازێکی یەکجاری: یەک باشە؛ ئەو ڕۆژەی شاشەی دووەم یان پێویست بوو، دابەشی بکە.
  • ئەپێکی وەک Bazaar Cart: سێ لە یەک .
  • گەورە بە چەندین تیم: سەرەتا بەش، لە ناو هەر بەشێکدا. Fowler هەروەها ئاگادار دەکاتەوە لە دابەشکردنی تیم بەپێی : بەرنامەساز پێویست ناکات هەموو شت بزانێت، بەڵام تیم دەبێت هەموو شتی تێدا بێت.

Common mistakesهەڵە باوەکان

Here is the third mistake for real. Someone adds a confirmation question inside the service, then a test runs it on a computer with no keyboard input, as a test server does:

ئەمە هەڵەی سێیەمە بە ڕاستی. کەسێک پرسیارێکی دڵنیاکردنەوە دەخاتە ناو ، پاشان لەسەر کۆمپیوتەرێک بێ تەختەکلیل کاری پێ دەکات، وەک تاقیکردنەوە:

Common mistake: UI work inside the business layerهەڵەی باو: کاری چینی نێوبەستەی بەکارهێنەر لە ناو چینی لۆجیکی کاروبارداmistake/chatty_service.pyPYTHONType in a terminalلە تێرمیناڵدا بینووسە
from bazaar_cart.services import OrderService


class ChattyOrderService(OrderService):
    def place_order(self, items):
        order = self.price_order(items)
        print(f"Total: {order.total:,} IQD")           # UI work...
        if input("Confirm (y/n)? ") != "y":            # ...inside the business layer
            return 0
        for code, grams in items:
            self.repo.reduce_stock(code, grams)
        return order.total

Fix: keep the question in cli.py, as in step 3: the UI shows the bill from price_order(), asks «Confirm (y/n)?» and only then calls place_order(). The service stays silent and testable.

چارەسەر: پرسیارەکە لە cli.pyـدا بهێڵەوە، وەک لە هەنگاوی 3: پسوولەکە لە price_order()ـەوە پیشان دەدات، دەپرسێت «⁦Confirm (y/n)?⁩» و تەنیا پاشان place_order() بانگ دەکات. بێدەنگ و تاقیکراو دەمێنێتەوە.

Exercisesڕاهێنانەکان

ExerciseڕاهێنانSort the linesدێڕەکان پۆلێن بکە

Say which layer each line belongs in: (a) print(f"Total {order.total:,} IQD"); (b) if goods_total >= 100_000: discount = ...; (c) db.execute("SELECT stock_g FROM products WHERE code = ?", (code,)); (d) if not text.isdigit(): print("Please type a number"); (e) return Product(*row).

بڵێ هەر دێڕێک هی کام : ⁦(a)⁩ print(f"Total {order.total:,} IQD")؛ ⁦(b)⁩ if goods_total >= 100_000: discount = ...؛ ⁦(c)⁩ db.execute("SELECT stock_g FROM products WHERE code = ?", (code,))؛ ⁦(d)⁩ if not text.isdigit(): print("Please type a number")؛ ⁦(e)⁩ return Product(*row).

Show answerوەڵامەکە پیشان بدە

(a) UI layer: it shows. (b) Business logic layer: it is rule R5. (c) Data access layer: SQL. (d) UI layer: a friendly format check. (e) Data access layer: it turns a row into an object.

⁦(a)⁩ : پیشان دەدات. ⁦(b)⁩ : یاسای ⁦R5⁩ـە. ⁦(c)⁩ : . ⁦(d)⁩ : پشکنینێکی دۆستانەی شێوە. ⁦(e)⁩ : ڕیزێک دەکات بە .

ExerciseڕاهێنانDilan finds a bugدیلان باگێک دەدۆزێتەوە

There are 3,000 g of honey in stock. Dilan orders honey 500 g and then honey 2,600 g in the same order, and the program accepts it (3,100 g!). Run it, find which layer must change, fix it and add a test.

⁦3,000⁩ گرام هەنگوین لە کۆگادا هەیە. دیلان لە یەک داواکاریدا ⁦500⁩ گرام هەنگوین و پاشان ⁦2,600⁩ گرام هەنگوین داوا دەکات و پەسەندی دەکات (⁦3,100⁩ گرام!). کاری پێ بکە، بدۆزەوە کام دەبێت بگۆڕێت، چاکی بکە و زیاد بکە.

Hintئاماژە

Each line passes R3 alone. What does the rule really say about the whole order?

هەر دێڕێک بە تەنیا ⁦R3⁩ تێدەپەڕێنێت. یاساکە بە ڕاستی لەبارەی هەموو داواکارییەکە چی دەڵێت؟

Show answerوەڵامەکە پیشان بدە
The bug, before the fixباگەکە، پێش چاککردنTEXTType in a terminalلە تێرمیناڵدا بینووسە
Product code (empty to finish): honey
Grams: 500
Product code (empty to finish): honey
Grams: 2600
Product code (empty to finish): 
  Honey        500 g  15,000 IQD
  Honey       2600 g  78,000 IQD
  Goods 93,000 + delivery 0 = 93,000 IQD
Confirm (y/n)? y
Order placed: 93,000 IQD. Thank you!

R3 is a business rule, so the fix goes into services.py: add up the grams of each product over all lines before comparing with the stock.

⁦R3⁩ یاسای کارە، بۆیە چاککردنەکە دەچێتە services.py: پێش بەراوردکردن لەگەڵ بڕی کۆگا، گرامی هەر بەرهەمێک لە هەموو دێڕەکاندا کۆ بکەرەوە.

The fixed methodمیتۆدە چاککراوەکەbazaar_cart/services.pyPYTHON
    def price_order(self, items):
        """Check every line (R2, R3) and price the order (R1, R4). Saves nothing."""
        lines = []
        wanted = {}  # grams per product code, over all lines
        for code, grams in items:
            product = self.repo.get(code)
            if product is None:
                raise ValueError(f"unknown product: {code}")
            if not MIN_GRAMS <= grams <= MAX_GRAMS:
                raise ValueError(f"{product.name}: {grams} g is outside 100-10,000 g")
            wanted[code] = wanted.get(code, 0) + grams
            if wanted[code] > product.stock_g:
                raise ValueError(f"{product.name}: only {product.stock_g:,} g in stock")
            lines.append(OrderLine(product.name, grams,
                                   line_price(product.price_per_kg, grams)))
        goods = sum(line.price for line in lines)
        return Order(lines, goods, delivery_fee(goods))
A regression test for the bugتاقیکردنەوەی پاشەکشە بۆ باگەکەtests/test_services.pyPYTHON
    def test_r3_two_lines_of_one_product(self):
        with self.assertRaises(ValueError):
            self.service.price_order([("honey", 500), ("honey", 2600)])
After the fixدوای چاککردنTEXTType in a terminalلە تێرمیناڵدا بینووسە
Product code (empty to finish): honey
Grams: 500
Product code (empty to finish): honey
Grams: 2600
Product code (empty to finish): 
Sorry, Honey: only 3,000 g in stock
ExerciseڕاهێنانAdd R5 in the right place⁦R5⁩ لە شوێنی دروستدا زیاد بکە

Version 1.1.0 adds R5: 5 % off goods totals of 100,000 IQD or more, rounded up to 250 IQD. Which files change, and which do not? Sketch the change.

وەشانی ⁦1.1.0⁩ یاسای ⁦R5⁩ زیاد دەکات: داشکاندنی 5 لە سەد بۆ کۆی کاڵای ⁦100,000⁩ دینار یان زیاتر، کە بۆ سەرەوە دەبرێت بۆ ⁦250⁩ دینار (). کام دەگۆڕێن و کامیان ناگۆڕێن؟ گۆڕانەکە بە کورتی بنووسە.

Show answerوەڵامەکە پیشان بدە

Only services.py and its tests change: add a discount(goods_total) function that returns round_up_250(goods_total * 5 / 100) from 100,000 IQD and 0 below, store it in the Order and subtract it in total. cli.py and receipt.py need at most one new line to show the discount; repository.py does not change. Write tests for 99,750 and 100,000 IQD first (test first).

تەنیا services.py و دەگۆڕێن: discount(goods_total) زیاد بکە کە لە ⁦100,000⁩ دینارەوە round_up_250(goods_total * 5 / 100) دەگەڕێنێتەوە و لە خوارتر 0، لە Orderـدا هەڵی بگرە و لە totalـدا لێی دەربکە. cli.py و receipt.py زۆرترین یەک دێڕی نوێیان دەوێت بۆ پیشاندانی داشکاندنەکە؛ repository.py ناگۆڕێت. سەرەتا بۆ ⁦99,750⁩ و ⁦100,000⁩ دینار بنووسە (سەرەتا حاڵەتی تاقیکردنەوە).

ExerciseڕاهێنانLayers in a PHP pageچینەکان لە لاپەڕەیەکی PHPـدا

Open create.php in the CRUD application. List which lines are presentation, which are rules (validation) and which are data access, and say which new files you would create.

create.php لە بەرنامەی CRUDـدا بکەرەوە. بڵێ کام دێڕانە پیشاندانن، کامانە یاسان () و کامانە دەستڕاگەیشتن بە . پاشان بڵێ چ نوێ دروست دەکەیت.

Show answerوەڵامەکە پیشان بدە

The HTML form and the echoed messages are presentation; the checks on the submitted fields are business rules; the mysqli/PDO INSERT is data access. A layered version keeps the form in create.php, moves the checks into a StudentService.php (it throws an exception with a message) and the INSERT into a StudentRepository.php. The page then calls only the service.

فۆرمی و ئەو پەیامانەی بە echo چاپ دەکرێن پیشاندانن؛ پشکنینی خانە نێردراوەکان یاسای کارن؛ INSERTـی mysqli/PDO دەستڕاگەیشتنە بە . وەشانێکی چینبەندکراو فۆرمەکە لە create.phpـدا دەهێڵێتەوە، پشکنینەکان دەگوازێتەوە بۆ StudentService.php (کە بە پەیامێکەوە هەڵدەدات) و INSERT بۆ StudentRepository.php. پاشان لاپەڕەکە تەنیا بانگ دەکات.

Summaryپوختە

  • Business logic is the real job's rules (R1–R5, stock, who may do what). It is the reason the software exists.
  • Three layers: UI (asks and shows), business logic (rules), data access (storage). Calls go down one step; results come back up.
  • Layers are logical; tiers are machines. The same three layers can run on one laptop or on three servers (three tiers).
  • A repository hides SQL; a service holds the rules; the UI only asks, shows and passes errors on. A second screen reuses every rule.
  • Rules are tested alone with a fake repository; the UI checks format kindly, the business layer enforces rules always; one rule has one home (DRY).
  • Do not layer a tiny script; split huge systems by feature first, then layer each feature.
  • یاساکانی کارە ڕاستەقینەکەیە (⁦R1⁩ تا ⁦R5⁩، کۆگا، کێ دەتوانێت چی بکات). هۆی بوونی .
  • سێ : (دەپرسێت و پیشان دەدات)، (یاسا)، (هەڵگرتن). بانگکردن یەک هەنگاو بەرەو خوار دەچێت؛ ئەنجام دەگەڕێتەوە سەرەوە.
  • لۆجیکین؛ چینی ئامێر ئامێرن. هەمان سێ دەتوانن لەسەر یەک یان سێ کار بکەن (سێ چین).
  • دەشارێتەوە؛ یاساکان هەڵدەگرێت؛ تەنیا دەپرسێت، پیشان دەدات و هەڵەکان دەگەیەنێت. شاشەی دووەم هەموو یاساکان دووپاتە بەکار دەهێنێتەوە.
  • یاساکان بە ساختە بە تەنیا تاقی دەکرێنەوە؛ بە نەرمی شێوە دەپشکنێت، هەمیشە یاسا جێبەجێ دەکات؛ هەر یاسایەک یەک ماڵی هەیە ⁦(DRY)⁩.
  • بچووک چینبەند مەکە؛ زۆر گەورە سەرەتا بەپێی بەش دابەش بکە، پاشان هەر بەشێک چینبەند بکە.

Next: the UI layer itself can be organised in three classic ways: MVC, MVP and MVVM.

داهاتوو: خۆی دەتوانرێت بە سێ ڕێگەی ناسراو ڕێک بخرێت: MVC، MVP و MVVM.

Lesson complete!وانەکە تەواو بوو!

Well done. Review the key terms, then go on to the next lesson.دەستخۆش. سەیرێکی زاراوە سەرەکییەکان بکەرەوە، پاشان بڕۆ بۆ وانەی داهاتوو.

Key termsزاراوە سەرەکییەکان

Next lessonوانەی داهاتوو

MVC, MVP and MVVM: Three Ways to Organise the ScreenMVC، MVP و MVVM: سێ ڕێگە بۆ ڕێکخستنی شاشە

References and further readingسەرچاوەکان و خوێندنەوەی زیاتر

This lecture is a first edition. The Kurdish text and terminology are waiting for review by Kurdish IT teachers.ئەم وانەیە چاپی یەکەمە. دەقە کوردییەکە و زاراوەکانی چاوەڕێی پێداچوونەوەی مامۆستایانی کوردی بواری IT ن.

Appearanceڕووکار

Modeدۆخ
Styleشێواز
Coloursڕەنگەکان

Saved on this device only.تەنیا لەسەر ئەم ئامێرە پاشەکەوت دەکرێت.