Your system is not unique - The power of business archetypes

Your system is not unique - The power of business archetypes

Are you joining a new greenfield project and hearing from the business side that ‘our domain is absolutely unique’? When Product Owners explain with a twinkle in their eyes that the processes in this company are so specific that no ready-made solution can handle them, and that design patterns from books are nothing but fairy tales? I know this too. And usually, after three months of analysis, it turns out that we are building another online store, except that instead of shoes, we are selling insurance, conference room reservations, or API access.

Today, we will talk about how to stop reinventing the wheel and start seeing the matrix, i.e., business archetypes. Instead of building silos for specific features, I will show you how to map a complex domain onto eight universal building blocks. To avoid repeating examples from logistics or shops, we will take a look at something close to our everyday work - a system for managing a network of co-working spaces. Let's call it ‘DeskHero’.

The illusion of complexity

Imagine that you have to handle three scenarios in DeskHero. The first is John, a freelancer who wants to rent a Hot Desk for one day. The second is UnicornAI, a start-up that rents a private office for a year with monthly payments. The third is the option to purchase a Virtual Address for the company.

The naive approach, which I have seen in dozens of legacy systems, is to create separate tables and services for each entity. We would have a desk_bookings table for John, office_leases for the start-up, and virtual_service_subscriptions for the address. On top of that, separate controllers, separate if-statements in the billing code, and separate availability checking logic. The code bloats, and adding a new service, such as ‘hourly room rental,’ requires digging through half the system.

But what if we looked at it differently? All these services are simply products. Whether it's a desk, a room or an address, it's an item in the catalogue. Mark and UnicornAI are simply parties to the transaction. What connects them to the company is the order, which gives them certain entitlements. Do you see the pattern?

The product catalogue is the foundation

It all starts with defining what we are actually selling. Instead of rigid classes, we need a flexible product catalogue. In our co-working system, the product is both physical access to a desk and a virtual service.

# app/models/product.rb
class Product < ApplicationRecord
  # We use STI or a 'type' column to differentiate simplified behavior
  # but structurally, they are all products in the catalog.

  has_many :product_attributes
  has_many :pricing_rules

  # Example attributes:
  # name: "Open Space Daily Pass"
  # product_type: "service" (vs "physical_good")
  # sku: "DESK-HOT-001"
end

# app/services/price_calculator.rb
class PriceCalculator
  def initialize(product, context = {})
    @product = product
    @context = context # context contains user_segment, day_of_week, etc.
  end

  def calculate
    # Pricing archetype: logic is decoupled from the product itself.
    # We apply active pricing policies based on the context.
    base_price = @product.pricing_rules.find_by(active: true).amount
    apply_discounts(base_price)
  end

  private

  def apply_discounts(price)
    # logic for applying discounts based on Party archetype (e.g., loyal member)
    price
  end
end

Note that PriceCalculator does not care whether we are pricing a desk or a coffee. It is only interested in pricing policy. Thanks to this, when the business comes up with ‘Happy Hours for conference rooms’, you only need to add a new pricing rule instead of digging into the room booking code.

From order to entitlement

This is where many engineers make the mistake of linking the fact of purchase with the fact of service delivery. In archetypal architecture, these are two different worlds. When John buys a ‘Day Pass,’ he places an Order. But the gate system in the office building should not query the database for orders. That's too much coupling.

This is where the Entitlements archetype comes in. The order is only a trigger that creates an Entitlement after payment. The Entitlement says: ‘User X has the right to enter zone Y during hours Z’.

-- Raw SQL schema example for Entitlements
-- This table is the source of truth for access control systems (IoT gates, Wi-Fi login)

CREATE TABLE entitlements (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    party_id UUID NOT NULL,            -- Who has the right? (John)
    resource_id UUID NOT NULL,         -- What resource? (Zone A, Meeting Room B)
    source_order_id UUID,              -- Traceability back to the Order
    valid_from TIMESTAMP NOT NULL,
    valid_to TIMESTAMP,                -- NULL could mean perpetual access
    status VARCHAR(50) NOT NULL,       -- 'active', 'expired', 'suspended'
    quota_limit INT,                   -- Optional: e.g., 5 hours of meeting room usage
    quota_used INT DEFAULT 0
);

Thanks to this separation, if John does not pay the invoice (Billing), the billing system sends an event that changes the status in the entitlements table to suspended. The turnstile does not need to know about invoices. It only asks for entitlement. This is a clear separation of responsibilities.

Inventory in the world of services

You may ask, what about inventory in coworking? After all, we don't send parcels. Yes, but inventory is not just boxes on a shelf. In our case, inventory is the availability of resources over time. Desk number 42 has its own ‘availability calendar’.

When John places an order, the system must make a reservation in the Inventory module. If we sell virtual addresses, inventory can be infinite, but if we sell desks, it is strictly limited. It is the same mechanism that handles inventory in e-commerce, only here the dimension is time.

class InventoryManager
  def reserve!(resource_id, time_slot)
    ActiveRecord::Base.transaction do
      # We lock the inventory slot to prevent overbooking (race conditions)
      slot = InventorySlot.lock.find_by(resource_id: resource_id, time_range: time_slot)

      if slot.available?
        slot.update!(status: 'reserved', reservation_id: generate_uuid)
      else
        raise InventoryError, "Resource strictly unavailable"
      end
    end
  end
end

Party and Billing - who and for how much

Finally, we tie everything together with entities (Party) and settlements (Billing). In our system, Party is not just a users table. It is an abstraction that can be a natural person, a company, or even another system (e.g. a brokerage office). This makes the billing system generic. We do not create a separate invoicing mechanism for start-ups and receipts for freelancers. We have a single process that, based on the attributes of Party, decides what document to generate and how to post the transaction (Accounting).

Summary

Understanding that your complex system is actually a set of eight archetypes: Product Catalogue, Pricing, Ordering, Inventory, Billing, Accounting, Entitlements, and Party, gives you incredible freedom. Instead of writing boilerplate code to handle the ‘specific’ logic of desk reservations, you configure existing modules. Your engineering contribution shifts to where the real value lies - for example, building advanced algorithms for optimising space utilisation, rather than hammering out yet another CRUD for orders. Don't let anyone convince you that your project is unique. It's a solid, engineered construction made from building blocks that we all know well.

Happy archetyping!