المفاهيم
هنفهم إيه؟ هتتعلم تختار حدود للكود تخلي تغيير قاعدة خصم أو مزود دفع مايفرضش تعديل أجزاء مالهاش علاقة. هنبدأ بمسؤولية كل class وعقد الـ interface، وبعدها نربط المبادئ بقرارات تنظيم تطبيق وتكلفتها.
قبل ما تبدأ: الـ class نوع بيجمع بيانات وسلوك، والـ method عملية، والـ interface عقد يحدد عمليات يقدر caller يطلبها. Dependency هي حاجة class محتاجاها عشان تشتغل. لو المصطلحات جديدة، كفاية تفهم الأمثلة الصغيرة قبل أسماء المبادئ.
المصطلحات اللي تحتها خط منقّط تقدر تضغط عليها لمعرفة معناها، وترجع تكمل من نفس مكانك.
#S: Single Responsibility و CohesionS: single responsibility and cohesion
لو تغيير شكل الإيميل بيخلّيك تلمس حساب الفاتورة، المسؤوليات متلخبطة. SRP بتجمع الكود اللي بيتغير لنفس السبب وتفصل اللي بيتغير لأسباب مختلفة؛ مش معناها method واحدة لكل class. Cohesion ترابط مسؤوليات الوحدة من جوه؛ coupling اعتمادها على وحدات تانية. عايزين cohesion عالية واعتمادات قليلة وواضحة.
public sealed class InvoiceService
{
public string BuildEmail(decimal subtotal) =>
$"<p>Total: {subtotal * 1.14m}</p>";
}public sealed class InvoiceTotals(decimal taxRate)
{
public decimal Calculate(decimal subtotal) => subtotal * (1 + taxRate); // (1)
}
public sealed class InvoiceEmail
{
public string Format(decimal total) => $"Total: {total:0.00}"; // (2)
}- (1) تغيير سياسة الضريبة ليه مكان محدد؛ الـ rate هنا input مش قانون ضريبي عام.
- (2) شكل الرسالة يتغير من غير تغيير المعادلة.
اختبار القواعد بقى من غير email server. لكن ماتقسمش كل سطر في class؛ لو التغيير دايمًا بيحصل مع بعض، الفصل الزيادة بيصعّب تتبع التدفق.
#O: Open/Closed عند نقطة التغييرO: open/closed at a real variation point
لو كل طريقة خصم جديدة بتعدّل نفس switch كبيرة، عندك نقطة تغيير متكررة. OCP بتخلي إضافة نوع جديد تتم بimplementation جديدة للعقد المستقر. مش معناها إن الكود ممنوع يتعدل؛ اكتشاف bug أو تغيير العقد يستلزم تعديل واختبار.
decimal Discount(decimal total, string kind) => kind switch
{
"percent" => total * 0.1m,
"fixed" => Math.Min(total, 50m),
_ => throw new ArgumentException("Unknown discount")
};public interface IDiscountRule { decimal Calculate(decimal total); } // (1)
public sealed class PercentDiscount(decimal rate) : IDiscountRule
{
public decimal Calculate(decimal total) => total * rate;
}
public sealed class DiscountCalculator(IDiscountRule rule)
{
public decimal Calculate(decimal total) => rule.Calculate(total); // (2)
}- (1) العقد لازم يوضح نطاق total وrate وقاعدة التقريب في النظام الحقيقي.
- (2) اختيار rule يحصل في composition root أو factory؛ الحساب مش محتاج يعرف كل الأنواع.
Strategy و أدوات تطبيق. switch صغيرة على حالات ثابتة ومغلقة ممكن تبقى أوضح من عشر interfaces. ماتعملش extension point لمجرد احتمال بعيد.
#L: Liskov و Rectangle/SquareL: Liskov and rectangle/square
الوراثة وعد سلوكي: subtype لازم تشتغل مكان base من غير ما تكسر توقعات العميل. ممنوع تقوّي preconditions أو تضعّف postconditions اللي العقد وعد بيها. المربع مستطيل رياضيًا، لكن مش مستطيل mutable بيقبل تغيير العرض والطول بشكل مستقل.
public class Rectangle
{
public virtual int Width { get; set; }
public virtual int Height { get; set; }
}
public sealed class Square : Rectangle
{
public override int Width
{
get => base.Width;
set { base.Width = value; base.Height = value; }
}
public override int Height
{
get => base.Height;
set { base.Width = value; base.Height = value; }
}
}
// For a Rectangle, the client expects 5 * 10 = 50.
Rectangle shape = new Square();
shape.Width = 5;
shape.Height = 10; // Width also becomes 10; area = 100public interface IShape { int Area { get; } } // (1)
public sealed record Rectangle(int Width, int Height) : IShape
{
public int Area => Width * Height;
}
public sealed record Square(int Side) : IShape
{
public int Area => Side * Side; // (2)
}- (1) العقد الجديد مايعدش بتغيير أبعاد مستقلة.
- (2) كل shape تحسب مساحتها من تمثيل مناسب. الأبعاد الموجبة والتحقق من overflow قواعد إضافية حسب الاستخدام.
دي بدائل مستقلة، مش تعريفات تتجمع في نفس الملف. ماتحكمش على LSP بمجرد وجود exception؛ السؤال هل الـ exception أو السلوك خالفوا العقد؟
#I: Interface Segregation بالـ capabilitiesI: interface segregation through capabilities
الـ interface عقد صغير بيقول للعميل يقدر يطلب إيه من object. لو عقد الدفع وعد إن كل مزود يقدر يعمل refund، هيبقى وعد كاذب لمزود كاش مايعرفش يرجّع عملية إلكترونية. ISP بتقسم العقود حسب القدرة اللي العميل محتاجها فعلاً، مش حسب عدد methods ثابت.
public interface IPaymentProvider
{
Task ChargeAsync(decimal amount);
Task RefundAsync(string paymentId);
}
public sealed class CashProvider : IPaymentProvider
{
public Task ChargeAsync(decimal amount) => Task.CompletedTask;
public Task RefundAsync(string paymentId) => throw new NotSupportedException();
}public interface IChargeProvider
{
Task ChargeAsync(decimal amount, CancellationToken ct);
}
public interface IRefundProvider
{
Task RefundAsync(string paymentId, CancellationToken ct); // (1)
}
public sealed class RefundPayment(IRefundProvider refunds)
{
public Task Run(string paymentId, CancellationToken ct) =>
refunds.RefundAsync(paymentId, ct); // (2)
}- (1) اللي يدعم refund بس هو اللي ينفذ العقد.
- (2) use case تطلب refund capability مباشرة بدل type checks متناثرة.
الكود الغلط mock لسلوك مش مكتمل، مش تنفيذ دفع حقيقي. ممكن provider تنفذ العقدين؛ مش لازم تفصلها في objectين. لو العقود بتتغير مع بعض ونفس العملاء محتاجينها، تقسيمها الزيادة بيزود الضوضاء.
#D: Dependency Inversion مش مجرد InjectionD: dependency inversion is more than injection
- OrderService يحتاج ينفذ دفعًا، لكن قواعد إنشاء الطلب مش محتاجة تعرف اسم شركة الدفع أو تفاصيل الخاصة بها.
- عرّف عقدًا مثل IPaymentGateway بالعملية التي تحتاجها قواعد الطلب. محول الشركة ينفذ العقد، وخدمة الطلب تستقبل التنفيذ من الخارج.
- يخص اتجاه الاعتماد على العقد، و طريقة لتوصيل التنفيذ للكلاس. تقدر تستخدم DI مع اعتماد مباشر على كلاس بنية تحتية؛ إذن وجود DI وحده مش دليل إن DIP متحقق.
في تأكيد order، policy هي قاعدة التطبيق: إيه اللي يحصل بعد التأكيد؟ Infrastructure هي تفاصيل الوسيلة، زي الكتابة لملف أو استخدام خارجي. لو checkout اختار الوسيلة بنفسه، تغيير التخزين يجرّ تعديل قاعدة العمل. Abstraction هنا contract صغير يصف احتياج checkout من غير ما يحدد الوسيلة. مبدأ DIP بيخلّي قاعدة العمل تعتمد على الـ contract، والوسيلة تنفذه. Dependency injection (DI) طريقة توصيل التنفيذ من بره؛ حقن class ملموسة مباشرة لوحده مش DIP.
public sealed class ConfirmOrder
{
public void Run(int id) =>
File.AppendAllText("orders.log", $"Confirmed {id}\n");
}// Application project
public interface IOrderNotifications { void Confirmed(int orderId); } // (1)
public sealed class ConfirmOrder(IOrderNotifications notifications)
{
public void Run(int id) => notifications.Confirmed(id);
}
// Infrastructure project references Application.
public sealed class FileOrderNotifications(string path) : IOrderNotifications
{
public void Confirmed(int id) =>
File.AppendAllText(path, $"Confirmed {id}\n"); // (2)
}
// Composition root supplies path and implementation. // (3)- (1) الـ application بتحدد احتياجها من غير File .
- (2) adapter بتنفذ التفصيلة؛ في الحقيقي هتحدد reliability ومتطلبات التزامن.
- (3) اختيار التنفيذ في startup، مش business logic.
المثال لتوضيح اتجاه الاعتماد، مش ضمان durable notification. ما تعملش interface لكل utility خالصة؛ اعزل الحدود اللي لها سبب حقيقي للتغيير أو الاختبار.
#Layered و Clean و Hexagonal و Onion و Vertical SlicesLayered, Clean, Hexagonal, Onion and vertical slices
إضافة feature بتحتاج رحلة في عشر فولدرات؟ أو domain rules مربوطة بـ ؟ اختار التنظيم حسب المشكلة. الأسماء دي متداخلة، ومش frameworks لازم تختار واحدة بس منها.
| Style | الفكرة | التكلفة |
|---|---|---|
| Layered | Presentation ثم business ثم data؛ فصل حسب الدور التقني | pass-through layers واعتماد business على data لو التصميم كده |
| Clean | source dependencies لجوه نحو domain وuse cases | ports وmapping ومزيد من الملفات |
| Hexagonal | application في النص؛ inbound وoutbound ports مع adapters | تحديد عقود الحدود محتاج عناية |
| Onion | domain core وحلقات اعتماد للداخل | نفس خطر abstraction الزيادة |
| Vertical slices | تنظيم حسب use case: endpoint وhandler وdata access | تكرار لو قواعد مشتركة مش متجمعة في domain |
Clean source references:
Shop.Application → Shop.Domain (1)
Shop.Infrastructure → Shop.Application, Shop.Domain
Shop.Api → Shop.Application, Shop.Infrastructure (2)
Inside Application, organized by feature:
Features/Orders/PlaceOrder/Handler.cs (3)
Features/Orders/GetSummary/Handler.cs
- (1) domain مش محتاجة تعرف أو ASP.NET.
- (2) الـ host يشوف infrastructure للتسجيل؛ runtime call ممكن تروح adapter عكس اتجاه source reference، عن طريق interface.
- (3) slices جوه boundaries بتاعة Clean؛ الاتنين ممكن يتجمعوا.
تبديل database مش بالضرورة تعديل ملف واحد؛ و semantics ممكن تتسرب. الفايدة الأساسية حدود واعتمادات واضحة. صغير ممكن يحتاج slices بسيطة بس. Microsoft architecture guide.
#DDD strategic: حدود المعنىStrategic DDD: boundaries of meaning
كلمة Product عند فريق catalog معناها اسم وصور، وعند inventory معناها رصيد وموقع تخزين. إجبار الاتنين على giant model بيخلق coupling. حدود model ومعنى المصطلحات، وubiquitous language نفس اللغة اللي الخبراء والكود بيستخدموها.
Context map بتسجل علاقة الـ contexts: مين upstream، ومين downstream، ومين مالك العقد، وهل فيه published language أو anti-corruption layer للترجمة. Context مش لازم microservice، ومش لازم يطابق جدول أو فريق بالظبط.
Catalog [upstream] -- ProductSnapshot v1 --> Sales [downstream] (1)
Payment provider -- external status --> ACL --> Sales.PaymentState (2)
Inventory owns availability; Catalog owns descriptions. (3)
- (1) Sales تستقبل snapshot محددة بدل reference على Catalog entity داخلية.
- (2) تترجم statuses ومفاهيم الجهة الخارجية، مش أسماء fields بس.
- (3) ملكية المعنى تحدد مين صاحب القرار، مش مكان الفولدر بس.
ابدأ بحوارات وأمثلة مع business experts. مش مسابقة أسماء classes؛ لو المجال بسيط وثابت، tactical patterns كتير ممكن ماتستاهلش تكلفتها.
#DDD tactical: حماية الـ invariantsTactical DDD: protecting invariants
لو كل controller بتقرر لوحدها هل order مدفوعة ينفع تتعدل، القاعدة هتختلف. Entity ليها هوية ثابتة، وvalue object بتتقارن بقيمتها. حدود consistency ليها root تتحكم في التعديل؛ مش object graph تضم كل حاجة مرتبطة بforeign key.
public sealed record OrderLine(Guid ProductId, int Quantity);
public sealed record OrderConfirmed(Guid OrderId);
public sealed class Order
{
private readonly List<OrderLine> _lines = [];
private readonly List<OrderConfirmed> _events = [];
public Guid Id { get; } = Guid.CreateVersion7();
public bool IsConfirmed { get; private set; }
public IReadOnlyList<OrderLine> Lines => _lines.AsReadOnly(); // (1)
public IReadOnlyList<OrderConfirmed> Events => _events.AsReadOnly();
public void AddLine(Guid productId, int quantity)
{
if (IsConfirmed) throw new InvalidOperationException("Order locked");
ArgumentOutOfRangeException.ThrowIfNegativeOrZero(quantity);
_lines.Add(new(productId, quantity)); // (2)
}
public void Confirm()
{
if (IsConfirmed) return;
if (_lines.Count == 0) throw new InvalidOperationException("Empty order");
IsConfirmed = true;
_events.Add(new(Id)); // (3)
}
}
- (1) wrapper read-only وخطوط ؛ مش مجرد interface ترجع List قابلة للـ cast.
- (2) التعديل يعدي على داخل root.
- (3) domain event بتسجل حاجة حصلت؛ مش بتبعت network call. بعد حفظها، publication الموثوق ممكن يحتاج .
| Building block | إمتى |
|---|---|
| Value object | Money بعملة وقيمة؛ immutable ومساواة بالقيم مع validation مناسب |
| Repository | تحميل وحفظ aggregate حسب عقد المجال، مش كل جدول لازم repository |
| Domain service | قاعدة business مالهاش مالك طبيعي في entity؛ مش مجرد تنسيق و |
| Factory | إنشاء aggregate معقد بحالة صحيحة من البداية |
المثال model صغير؛ التطبيق لازم يوصل exceptions المتوقعة لنتيجة واضحة، ويحمي التزامن في database. Encapsulation وحدها مش بتمنع concurrent updates. ماتحمّلش ملايين السطور في aggregate واحدة؛ اختار boundaries على أساس invariants.
#Modular monolith: حدود من غير شبكةModular monolith: boundaries without a network
لو الفريق محتاج فصل ملكية features لكن مش محتاج deploy مستقل، modular monolith بداية عملية: executable واحدة، modules بعقود معلنة وimplementation مخفية. فصل الفولدرات بس مش modularity لو كل واحد بيقرأ جداول التاني.
Shop.Catalog.Contracts ICatalogPrices, ProductPriceDto (1)
Shop.Catalog internal entities and persistence
Shop.Sales.Contracts PlaceOrderRequest, OrderSummary
Shop.Sales references Catalog.Contracts only (2)
Shop.Host registers module implementations (3)
- (1) العقد صغير ومستقل عن entities.
- (2) modules تتكلم in-process عبر contracts أو events؛ compiler وarchitecture tests يحموا الحدود.
- (3) host يعمل composition؛ runtime واحد مش معناه singleton لكل services.
نفس database ممكنة مع schemas وملكية واضحة. مشتركة بين modules قرار مقصود يزيد coupling، ومش تلقائية لو كل module ليها . التوسع والنشر غالبًا للتطبيق كله. استخراج module لخدمة بعدين أسهل بحدود جيدة، لكنه لسه محتاج data migration ومعالجة network failures.
#Microservices: الاستقلال له تمنMicroservices: independence has a cost
لو جزء محتاج scaling أو release cadence أو ownership مستقلين، خدمة منفصلة ممكن تستاهل. لكن network call مش method call: وpartial failure وtimeouts وversioning جزء من التصميم.
| مكسب | شرط وتكلفة |
|---|---|
| Independent deployment وscaling | عقود backward compatible وpipelines ومراقبة مستقلة |
| Fault | timeouts وbulkheads؛ سلسلة sync calls ممكن تنشر العطل |
| Database per service | ملكية بيانات؛ ممنوع bypass عبر joins بين قواعد الخدمات |
| Team autonomy | حدود مفهومة واتفاق على events وschemas |
gateway routing وسياسات عامة، وBFF backend مخصوص لاحتياج client زي mobile. الاتنين مايتحولوش لمكان كل domain logic. Service discovery تربط اسم منطقي بعنوان instance؛ ممكن أو registry أو platform routing.
Mobile → Mobile BFF → Sales API → Sales-owned database (1)
→ outbox → broker → Inventory (2)
Old application → routing boundary → extracted Catalog API (3)
- (1) sync أو للرد الفوري، مع deadline وإدارة الفشل.
- (2) async messages تفصل الزمن وتحتاج و ومراقبة lag. database per service ملكية، مش لازم hardware مختلف.
- (3) Strangler fig: انقل feature تدريجيًا مع routing واضح بدل rewrite شامل؛ امنع dual writes غير المنسقة.
مش كل محتاجة service، ومش كل async communication . ابدأ بأقل deployment complexity تحقق المتطلبات، وراجع توزيع قبل التقسيم.
#CQRS: افصل احتياجات القراءة والكتابةCQRS: separate read and write needs
- إنشاء طلب يحتاج تحققًا من المخزون وحسابًا للسعر. عرض قائمة الطلبات يحتاج رقم الطلب وحالته فقط؛ احتياج الكتابة مختلف عن شكل القراءة.
- Command يعبر عن طلب تغيير، مثل PlaceOrder؛ Query يعبر عن سؤال، مثل GetOrderSummary. فصل المسارين يساعد كل واحد يخدم احتياجه بوضوح.
- ابدأ بالفصل داخل التطبيق لو ده كفاية، وممكن تشترك المسارات في قاعدة واحدة. قاعدة قراءة منفصلة أو تأخر تحديثها قرار إضافي، مش شرطًا لازمًا لمجرد استخدام .
تأكيد order محتاج ، لكن عرض summary محتاج خفيفة. CQRS تفصل models ومسارات commands عن queries. «Light» هنا نفس database، و«full» shorthand لنموذج فيه read store منفصلة؛ دي تسميات شائعة مش درجات رسمية. مش شرط.
// Application fragments; db exposes EF-mapped Orders.
public sealed record ConfirmOrder(int Id);
public sealed record OrderSummary(int Id, string Status);
// Command handler: load, enforce domain behavior, persist.
var order = await db.Orders.SingleAsync(o => o.Id == command.Id, ct);
order.Confirm();
await db.SaveChangesAsync(ct); // (1)
// Query handler: project directly, without loading the aggregate.
var summary = await db.Orders.AsNoTracking()
.Where(o => o.Id == id)
.Select(o => new OrderSummary(o.Id, o.Status))
.SingleOrDefaultAsync(ct); // (2)
- (1) command تستخدم domain behavior؛ ده fragment لموديل فيه Id من نوع int وStatus string، مستقل عن مثال الـ السابق.
- (2) query ترجع بس. projection دي مش بتحتوي entities أصلًا؛ AsNoTracking بتوضح النية.
| اختيار | الفايدة | التكلفة |
|---|---|---|
| نفس | فصل قواعد الكتابة عن شكل العرض | handlers وDTOs إضافية |
| Read store منفصلة | تشكيل وتوسيع القراءة بشكل مستقل | lag وrebuild وevent versioning ومراقبة |
| Event sourcing | الأحداث هي مصدر الحقيقة؛ راجع advanced topics | replay وتطور schemas وتصحيح التاريخ |
MediatR اختيار implementation مش CQRS نفسها، وملكية licensing تتراجع قبل الاعتماد. لمشروع بسيط فصل handlers شكليًا ممكن مايضيفش قيمة.
#ADRs و Architecture testsADRs and architecture tests
رسم الحدود من غير سبب موثّق أو فحص آلي بيتنسي مع الوقت. سجل قرار واحد: context، والبدائل، والقرار، والنتائج، والحالة. ماتكتبش documentation بتقول كل اختيار ممتاز؛ سجّل التمن وامتى تعيد النظر.
ADR 004: Keep checkout in the modular monolith (1)
Status: Accepted
Context: Stock reservation and order creation need one local commit.
Alternatives: Shared transaction; service split with saga.
Decision: Shared transaction with explicit module participation.
Consequence: More coupling; simpler atomic persistence.
Revisit: Inventory requires independent ownership or deployment.
// Test project: NetArchTest.Rules and the chosen xUnit package.
using NetArchTest.Rules;
using Xunit;
public sealed class BoundaryTests
{
[Fact]
public void Domain_does_not_depend_on_infrastructure()
{
var assembly = typeof(Shop.Domain.Order).Assembly; // (2)
var result = Types.InAssembly(assembly)
.ShouldNot().HaveDependencyOn("Shop.Infrastructure")
.GetResult();
Assert.True(result.IsSuccessful); // (3)
}
}
- (1) القرار محدود بسياقه وقابل للاستبدال بـ ADR جديدة، مش مسح التاريخ.
- (2) اختار assembly الصحيحة؛ test على assembly فاضية ممكن تدي ثقة كاذبة.
- (3) تفشل عند dependency ممنوعة. راجع failing types عند الفشل، وجرب violation مقصودة وقت إعداد القاعدة عشان تتأكد إنها فعالة.
NetArchTest وArchUnitNET أدوات لكتابة قواعد اعتماد؛ ماتثبتش domain correctness أو تمنع خام من تجاوز ملكية البيانات. لو بتسمح بـ Catalog.Contracts، ماتمنعش namespace prefix تشملها بالغلط؛ افصل implementation namespaces أو حدد المجموعة بدقة.
أسئلة الانترفيو
مفيش نتايج
راجع فهمك
ليه الاختيار ده مناسب؟ إمتى ممكن ماينفعش؟ وإيه اللي هيتغير لو الحمل زاد أو ظهر فشل؟
14.1How do you explain SOLID without just expanding the acronym?Junior
اربط كل مبدأ بمشكلة: أسباب تغيير مختلفة، switch بتكبر، subtype تكسر عقد، interface بتفرض قدرات وهمية، وسياسة مربوطة بتفاصيل.
I explain as managing change and contracts. Single responsibility keeps unrelated reasons to change separate. Open/closed supports new variants through stable extension points. Liskov requires implementations to honor the behavior clients expect. Interface segregation gives clients focused capabilities. Dependency inversion keeps business policy independent of infrastructure details through suitable abstractions. I apply them where they reduce real coupling; I would not add interfaces and layers everywhere merely to make the code look compliant.
14.2Does single responsibility mean one method per class?Junior
لأ. السبب الواحد للتغيير ممكن يحتاج كذا method. مجموعة methods بتخدم نفس business policy تبقى متماسكة؛ split لمجرد العدد مالوش معنى.
No. I look for reasons to change and the people or policies driving those changes. An invoice calculator can have several methods that implement one cohesive pricing policy. Mixing that policy with email formatting and file storage introduces unrelated reasons to edit it. Splitting every method into its own class would increase and obscure the workflow. I aim for cohesive modules whose responsibilities can be explained clearly, rather than treating method count as a design rule.
14.3What are coupling and cohesion?Junior
Cohesion ترابط المسؤوليات جوه الوحدة؛ coupling اعتمادها على غيرها. High cohesion مفيدة، وcoupling واضحة ومحدودة. Shared tables تعمل coupling حتى من غير class references.
Cohesion describes how strongly a module's responsibilities belong together. Coupling describes how much it depends on other modules and their details. I prefer high cohesion and dependencies that are few, explicit and stable. An interface can reduce implementation coupling, but it cannot remove semantic coupling if callers must know the provider's internal workflow. Shared tables and coordinated deployment can also create coupling even when the source code has very few direct references.
14.4What is an entity compared with a value object?Junior
Entity زي Order ليها هوية مستمرة. Money بتتحدد بالقيمة والعملة؛ value object عادة وبتفرض validation. record لوحدها مش كفاية لو properties قابلة للتعديل.
An entity is identified by an identity that remains meaningful while its attributes change, such as an order number. A value object is defined by its values, such as an amount and currency, and is normally . I use it to express and validate a domain concept rather than passing loosely related primitives. A C# record helps with value equality, but I still need to design validation and immutability; choosing the record keyword alone does not complete the model.
14.5How does the rectangle/square example violate Liskov substitution?Mid
المستطيل mutable واعد إن العرض والطول مستقلين. Square setter بتغير الاتنين؛ عميل يتوقع مساحة 50 يلاقي 100. الحل IShape بعقد مساحة من غير setters مستقلة.
The problem is the mutable contract, not the mathematical relationship. A rectangle client can set width to five and height to ten and expect an area of fifty. If Square overrides both setters to keep the sides equal, the same sequence produces one hundred. The subtype has broken the caller's expectation. I would model both behind an area-producing shape abstraction or use shapes, rather than forcing Square into an independent-dimension mutation contract.
14.6How does interface segregation help payment providers?Mid
قسّم charge وrefund. Refund use case تطلب IRefundProvider، ومزود cash ميتجبرش ينفذها ويرمي exception خلاف العقد.
I separate capabilities clients actually require, such as charging and refunding. A refund use case depends on IRefundProvider, while a charge-only provider never promises that capability. A provider supporting both can implement both interfaces. This avoids empty methods or unsupported-operation surprises and often helps satisfy Liskov as well. I would not split every method automatically; I look at which clients use the operations and whether those capabilities really vary independently.
14.7What is dependency inversion versus dependency injection?Mid
مبدأ اتجاه اعتماد السياسة على abstraction. تقنية تمرير dependency من بره. constructor فيها concrete تعتبر injection بس مش inversion للسياسة.
Dependency inversion is about source dependency direction: high-level policy depends on abstractions, and lower-level details implement those abstractions. Dependency injection is a construction technique that supplies collaborators from outside, usually through constructors. I can inject a concrete and still couple policy to infrastructure, so injection alone is not inversion. I define the contract around what the use case needs and let the composition root connect it to an adapter.
14.8What distinguishes Clean, Hexagonal and Onion architecture?Mid
كلهم بيعزلوا core عن التفاصيل. Clean تبرز domain وuse cases، Hexagonal ports/adapters، Onion حلقات domain في النص. أسماء مختلفة بتتقاطع؛ مش لازم implementation واحدة حرفية.
I see them as related approaches to protecting the application core. Clean Architecture emphasizes entities, use cases and inward source dependencies. Hexagonal Architecture emphasizes inbound and outbound ports with replaceable adapters. Onion Architecture places the domain at the center of dependency rings. Their vocabulary differs, but all can keep and persistence details outside core policy. I choose the boundaries needed by the application rather than adding every named layer from a diagram.
14.9Can vertical slices coexist with Clean Architecture?Mid
أيوة. Slices تنظيم use cases حسب feature، وClean اتجاه dependencies. ممكن feature folder جوه application تستخدم domain مشتركة وports واضحة.
Yes. Vertical slices organize code by use case, while Clean Architecture primarily constrains dependency direction. I can place a command handler, request and related application logic together within a feature while keeping domain in a shared domain model and infrastructure behind ports. The trade-off is balancing local feature changes against duplication of shared rules. For simple , a slice may access directly if that is an intentional boundary choice rather than an accidental dependency leak.
14.10What are bounded contexts and ubiquitous language?Mid
حدود فيها المصطلح ليه معنى واضح؛ Product في Catalog غير Inventory. لغة واحدة بين الخبراء والكود. Context map توضح العلاقات وملكية العقود والترجمة.
A is a boundary within which a model and its terminology have a consistent meaning. Product can mean descriptions and images in Catalog but quantities and locations in Inventory. Ubiquitous language is the shared vocabulary used by domain experts and code inside that boundary. I document relationships in a context map, including ownership and translation. I do not assume every context must become a microservice; modeling boundaries and deployment boundaries are separate decisions.
14.11What is an aggregate, and what does its root protect?Mid
مجموعة بتتغير كوحدة consistency؛ root تحمي . مش كل related table تتحط معاها. حماية الـ object لازم يكملها concurrency.
An is a cluster of domain objects treated as a consistency boundary, with a root controlling changes and enforcing . For an order, adding a line can check that the order is still editable and the quantity is valid. I do not include every related entity just because a foreign key connects it. Large aggregates increase loading and contention. I also enforce concurrency at persistence boundaries, because a well-encapsulated object alone cannot coordinate two application instances.
14.12How do domain services, application services and repositories differ?Mid
Domain service قاعدة business مش مناسبة لـ entity بعينها. Application service تنسق use case و. Repository تحميل وحفظ aggregates، مش مكان قواعد business كلها.
A domain service holds business behavior that has no natural owner in an entity or value object. An application service coordinates a use case, such as loading data, invoking domain behavior and committing changes. A repository provides domain-oriented persistence access to aggregates. I avoid putting all business logic in application services or repositories, because that leaves the model unable to protect its own rules. I also avoid inventing a domain service for every ordinary orchestration step.
14.13When would you choose a modular monolith over microservices?Senior
لو deploy واحد مناسب والفريق والـ محتاجين بساطة. Modules بعقود وملكية بيانات. لو فيه استقلال حقيقي في scaling أو teams ساعتها ناقش extraction وتكلفتها.
I choose a modular monolith when one deployment is acceptable and clear module ownership gives most of the needed separation. It keeps local calls and makes shared transactional work easier to coordinate. Microservices become attractive when independent deployment, scaling or team ownership provides a measurable benefit worth network and operational complexity. I enforce contracts and data ownership from the start, but I do not promise that extracting a module later is trivial; migration and distributed failure handling still require design.
14.14How do you keep a modular monolith from becoming a distributed-looking big ball of mud?Senior
Contracts صغيرة، implementation internal، منع table bypass، وarchitecture tests. Shared استثناء موثق، مش عذر إن كل module تعرف كل حاجة.
I make module contracts explicit and keep implementation types internal. Modules own their data and do not query each other's tables as a shortcut. Architecture tests check dependencies, while reviews and integration tests cover boundaries static analysis cannot see. If a shared is necessary, I document its coupling and require explicit participation rather than hidden cross-module access. I also avoid forcing in-process modules to imitate network services when that adds and complexity without a requirement.
14.15How do gateway, BFF, discovery and communication style fit microservices?Senior
Gateway routing وسياسات عامة، BFF احتياج client، discovery عناوين instances. Sync للرد المباشر وasync للفصل الزمني مع duplicates وlag؛ كل اختيار له failure model.
A gateway handles entry routing and shared policies; a BFF adapts backend interactions to one client's needs. Service discovery resolves logical service names to current instances. I use synchronous calls when an immediate response is required and asynchronous messages when temporal decoupling is useful. The latter still needs duplicate handling, ordering decisions and lag monitoring. I avoid long synchronous chains and keep domain rules in their owning services, rather than turning the gateway into a central business engine.
14.16Does CQRS require separate databases, MediatR or event sourcing?Senior
ولا واحدة شرط. ابدأ بفصل write model وread على نفس . Read store منفصلة تزيد lag وتعقيد. تخزين أحداث كمصدر حقيقة مختلف عن الفصل نفسه.
No. I can separate command behavior and query projections while using one database and injecting handlers directly. A separate read store is useful for independent read shaping or scaling, but introduces propagation lag, rebuilds and operational work. is another decision about making events the authoritative history rather than storing only current state. MediatR is one dispatch implementation. I introduce each choice for a concrete requirement instead of adopting a whole bundle because it is labeled .
14.17How would you migrate a monolith using the strangler fig pattern?Senior
اختار feature بحدود واضحة، route تدريجيًا، حدد مين مالك الكتابة والبيانات، وانقل مع rollback plan. Dual writes من غير تنسيق خطر.
I select a capability with clear ownership and introduce a routing boundary that can send its traffic to a new implementation while the old application continues serving the rest. I define authoritative data ownership and migrate reads and writes deliberately, with reconciliation and a rollback plan. Uncoordinated dual writes are a major risk. I also preserve contract compatibility and observe errors and during rollout. This reduces migration scope per step, but does not remove the need to untangle shared data.
14.18What should ADRs and architecture tests each contribute?Senior
تحفظ السبب والبدائل والتكلفة وrevisit trigger. Tests تفرض قواعد قابلة للفحص زي dependency direction. ماتثبتش جودة التصميم أو business correctness لوحدها.
An preserves why a decision was made, the alternatives considered, accepted costs and conditions for revisiting it. Architecture tests enforce machine-checkable boundaries, such as preventing domain dependencies on infrastructure. I use both because a test cannot explain the business reason and prose cannot stop dependency drift. I verify that the test selects real types and catches an intentional violation. Neither replaces behavioral tests, and static dependency rules cannot reliably detect every raw or runtime integration boundary violation.
اختبر نفسك
إجابات الاختبار وعلامات إكمال المحاور بتتحفظ على المتصفح ده، ومش بتتنقل لأجهزة تانية.
An invoice class changes for tax policy and email layout. Which principle is most directly involved?
A Square changes Width when a client sets Height through Rectangle. What contract is at risk?
What best demonstrates interface segregation?
Which statement about DI and DIP is accurate?
Can feature slices exist inside Clean Architecture boundaries?
Which DDD concept defines where Product has one consistent meaning?
What is an aggregate root primarily responsible for?
Which benefit most directly motivates a microservice split?
What is required by CQRS itself?
What should an architecture decision record preserve?