CtrlK
BlogDocsLog inGet started
Tessl Logo

evilissimo/modular-software-design

Use before implementing or refactoring software when the task requires designing module boundaries, APIs, layers, abstractions, services, repositories, adapters, or architecture. Helps coding agents reduce total system complexity by creating deep modules, hiding implementation knowledge, avoiding leakage and pass-through APIs, comparing alternative designs, documenting interfaces before coding, and critiquing existing architecture.

90

Quality

90%

Does it follow best practices?

Impact

No eval scenarios have been run

SecuritybySnyk

Passed

No known issues

Overview
Quality
Evals
Security
Files

module-design-examples.mdexamples/

Module Design Examples

Use these examples as patterns while designing or reviewing modules.

Example 1: Shallow Wrapper vs Deep Use Case Module

Bad shallow wrapper:

class OrderService:
    def create_order(self, request):
        return self.order_repository.create_order(request)

Why bad:

  • Adds a layer but no abstraction.
  • Leaks repository shape upward.
  • Hides no validation, policy, mapping, or invariant.

Better deep module:

class OrderPlacement:
    def place_order(self, customer_id, cart_id, payment_method_id):
        cart = self.carts.load(cart_id)
        customer = self.customers.load(customer_id)

        order = Order.from_cart(customer, cart)
        order.require_valid_shipping_address()
        order.require_available_inventory()

        authorization = self.payments.authorize(order.total, payment_method_id)
        reservation = self.inventory.reserve(order.items)

        return self.orders.save_confirmed(order, authorization, reservation)

Why better:

  • Caller intent is one operation: place_order.
  • Validation, payment, inventory, mapping, and persistence sequencing are hidden.
  • Future changes to ordering rules are localized.

Example 2: Leaky Domain Logic vs Hidden Representation

Bad leaky code:

def cancel_order(order_row):
    if order_row["status_code"] == 7:
        raise Exception("Already shipped")

    order_row["status_code"] = 9
    db.orders.update(order_row)

Why bad:

  • Domain logic depends on database representation.
  • Status codes leak into business logic.
  • Persistence and policy are mixed.

Better domain abstraction:

class Order:
    def cancel(self):
        if self.has_shipped():
            raise OrderCannotBeCancelled("Shipped orders cannot be cancelled.")

        self.status = OrderStatus.CANCELLED

Why better:

  • Domain code speaks domain language.
  • Storage representation is hidden by mapping code.
  • The cancellation invariant has one owner.

Example 3: Pass-Through Variables vs Owned Policy

Bad pass-through variables:

def generate_invoice(order, locale, tax_table, rounding_mode, currency_rules):
    ...

def email_invoice(order, locale, tax_table, rounding_mode, currency_rules):
    invoice = generate_invoice(order, locale, tax_table, rounding_mode, currency_rules)
    ...

Why bad:

  • Callers must know tax and currency details.
  • Parameters spread through layers that do not own them.
  • Every new rule expands public signatures.

Better owned policy:

class InvoiceGenerator:
    def generate_for(self, order):
        tax = self.tax_policy.calculate(order)
        total = self.currency_policy.round(order.subtotal + tax)
        return Invoice(order, tax, total)

Why better:

  • Policy details are hidden.
  • Callers provide the domain object.
  • Future tax/currency changes are localized.

Example 4: Temporal Decomposition vs Conceptual Decomposition

Bad temporal modules:

RequestParser
RequestValidator
RequestProcessor
ResponseBuilder

Problem:

  • Modules mirror execution order.
  • Shared domain knowledge leaks across steps.
  • Changes often touch every stage.

Better conceptual modules:

SubscriptionSignup
EligibilityPolicy
BillingAccount
WelcomeMessage

Why better:

  • Modules correspond to concepts and hidden decisions.
  • Eligibility, billing, and messaging can evolve independently.

Example 5: Duplicate Layer Abstractions

Bad layered pass-through stack:

UserController.get_user
UserService.get_user
UserManager.get_user
UserRepository.get_user
UserDAO.get_user

Problem:

  • Each layer exposes the same abstraction.
  • Most methods pass through.
  • The design adds files without reducing complexity.

Better distinct layers:

HTTP layer: translate request/response details.
Application layer: load an active member profile use case.
Domain layer: represent member eligibility and account status.
Infrastructure layer: hide user, membership, and permission storage.

Example operation:

class MemberProfileQuery:
    def load_active_profile(self, member_id):
        user = self.users.fetch(member_id)
        membership = self.memberships.fetch_current(member_id)
        permissions = self.permissions.resolve(member_id)

        if not user.active or not membership.current:
            return None

        return MemberProfile(user, membership, permissions)

Why better:

  • The application operation provides a new abstraction.
  • Callers do not coordinate repositories manually.
  • Storage and joining details stay below the interface.

examples

module-design-examples.md

README.md

SKILL.md

tile.json