orthogonality-principle
Use when designing modules, APIs, and system architecture requiring independent, non-overlapping components where changes in one don't affect others.
What this skill does
# Orthogonality Principle
Build systems where components are independent and changes don't ripple
unexpectedly.
## What is Orthogonality?
**Orthogonal** (from mathematics): Two lines are orthogonal if they're
at right angles - changing one doesn't affect the other.
**In software**: Components are orthogonal when changing one doesn't
require changing others. They are independent and non-overlapping.
### Benefits
- Changes are localized (less debugging)
- Easy to test in isolation
- Components are reusable
- Less coupling = less complexity
- Easier to understand and maintain
## Signs of Non-Orthogonality
### Red flags indicating components are NOT orthogonal
1. **Change amplification**: Changing one thing requires changing many others
2. **Shotgun surgery**: One feature scattered across many files
3. **Tight coupling**: Components know too much about each other
4. **Duplicate logic**: Same concept implemented multiple ways
5. **Cascading changes**: Change in A breaks B, C, D unexpectedly
## Achieving Orthogonality
### 1. Separation of Concerns
### Keep unrelated responsibilities separate
### Elixir Example
```elixir
# NON-ORTHOGONAL - Mixed concerns
defmodule UserController do
def create(conn, params) do
# Validation
if valid_email?(params["email"]) do
# Database
user = Repo.insert!(%User{email: params["email"]})
# External API
Stripe.create_customer(user.email)
# Notification
Email.send_welcome(user.email)
# Logging
Logger.info("Created user #{user.id}")
# Response
json(conn, %{user: user})
end
end
end
# Changing email format affects validation, database, Stripe, email!
# ORTHOGONAL - Separated concerns
defmodule UserController do
def create(conn, params) do
with {:ok, command} <- build_command(params),
{:ok, user} <- UserService.create(command) do
json(conn, %{user: user})
end
end
end
defmodule UserService do
def create(command) do
with {:ok, user} <- Repo.insert(User.changeset(command)),
:ok <- BillingService.setup_customer(user),
:ok <- NotificationService.welcome(user) do
{:ok, user}
end
end
end
# Now can change billing without touching notifications
# Can change notifications without touching database
# Each service is orthogonal
```
### TypeScript Example
```typescript
// NON-ORTHOGONAL - Everything in one component
function TaskList() {
const [tasks, setTasks] = useState<Task[]>([]);
const [filters, setFilters] = useState<Filters>({});
const [sorting, setSorting] = useState<Sort>({ field: 'date', dir: 'asc' });
// Data fetching
useEffect(() => {
fetch('/api/tasks').then(res => res.json()).then(setTasks);
}, []);
// Filtering logic
const filtered = tasks.filter(gig => {
if (filters.status && gig.status !== filters.status) return false;
if (filters.location && !gig.location.includes(filters.location)) return false;
return true;
});
// Sorting logic
const sorted = [...filtered].sort((a, b) => {
const aVal = a[sorting.field];
const bVal = b[sorting.field];
return sorting.dir === 'asc' ? aVal - bVal : bVal - aVal;
});
// Rendering
return (
<View>
{/* Filters UI */}
{/* Sorting UI */}
{/* List UI */}
</View>
);
}
// Changing filtering affects fetching, sorting, rendering!
// ORTHOGONAL - Separated concerns
function useTaskData() {
const [tasks, setTasks] = useState<Task[]>([]);
useEffect(() => {
fetch('/api/tasks').then(res => res.json()).then(setTasks);
}, []);
return tasks;
}
function useTaskFiltering(tasks: Task[], filters: Filters) {
return useMemo(() => {
return tasks.filter(gig => {
if (filters.status && gig.status !== filters.status) return false;
if (filters.location && !gig.location.includes(filters.location)) return false;
return true;
});
}, [tasks, filters]);
}
function useTaskSorting(tasks: Task[], sort: Sort) {
return useMemo(() => {
return [...tasks].sort((a, b) => {
const aVal = a[sort.field];
const bVal = b[sort.field];
return sort.dir === 'asc' ? aVal - bVal : bVal - aVal;
});
}, [tasks, sort]);
}
function TaskList() {
const allTasks = useTaskData();
const [filters, setFilters] = useState<Filters>({});
const [sort, setSort] = useState<Sort>({ field: 'date', dir: 'asc' });
const filtered = useTaskFiltering(allTasks, filters);
const sorted = useTaskSorting(filtered, sort);
return (
<View>
<TaskFilters filters={filters} onChange={setFilters} />
<TaskSorting sort={sort} onChange={setSort} />
<TaskCards tasks={sorted} />
</View>
);
}
// Now can change filtering without touching sorting
// Can change data fetching without touching UI
// Each concern is orthogonal
```
### 2. Interface Segregation
### Create focused, minimal interfaces
### Elixir Example (Interface Segregation)
```elixir
# NON-ORTHOGONAL - Fat interface
defmodule DataStore do
@callback get(key :: String.t()) :: {:ok, term()} | {:error, term()}
@callback set(key :: String.t(), value :: term()) :: :ok
@callback delete(key :: String.t()) :: :ok
@callback list_all() :: [term()]
@callback search(query :: String.t()) :: [term()]
@callback bulk_insert(items :: [term()]) :: :ok
@callback export_to_json() :: String.t()
@callback import_from_json(json :: String.t()) :: :ok
end
# Implementing simple cache requires implementing export/import!
# Not orthogonal - simple use cases coupled to complex ones
# ORTHOGONAL - Segregated interfaces
defmodule KeyValueStore do
@callback get(key :: String.t()) :: {:ok, term()} | {:error, term()}
@callback set(key :: String.t(), value :: term()) :: :ok
@callback delete(key :: String.t()) :: :ok
end
defmodule Searchable do
@callback search(query :: String.t()) :: [term()]
end
defmodule BulkOperations do
@callback bulk_insert(items :: [term()]) :: :ok
end
defmodule Exportable do
@callback export_to_json() :: String.t()
@callback import_from_json(json :: String.t()) :: :ok
end
# Simple cache implements only KeyValueStore
# Search index implements KeyValueStore + Searchable
# Each interface is orthogonal to others
```
### 3. Dependency Injection
### Don't hardcode dependencies - inject them
### Elixir Example (Dependency Injection)
```elixir
# NON-ORTHOGONAL - Hardcoded dependencies
defmodule OrderService do
def create_order(items) do
PaymentService.charge(items) # Coupled
InventoryService.reserve(items) # Coupled
EmailService.send_confirmation() # Coupled
end
end
# Can't test without real payment/inventory/email services
# Can't swap implementations
# ORTHOGONAL - Injected dependencies
defmodule OrderService do
def create_order(items, deps \\ default_deps()) do
with :ok <- deps.payment.charge(items),
:ok <- deps.inventory.reserve(items),
:ok <- deps.email.send_confirmation() do
:ok
end
end
defp default_deps do
%{
payment: PaymentService,
inventory: InventoryService,
email: EmailService
}
end
end
# Can test with mocks
test "creates order" do
deps = %{
payment: MockPayment,
inventory: MockInventory,
email: MockEmail
}
assert :ok = OrderService.create_order(items, deps)
end
# Each dependency is orthogonal - can change independently
```
### 4. Event-Driven Architecture
### Decouple through events instead of direct calls
### Elixir Example (Event-Driven Architecture)
```elixir
# NON-ORTHOGONAL - Direct coupling
defmodule UserService do
def create_user(attrs) do
{:ok, user} = Repo.insert(User.changeset(attrs))
# Directly coupled to all these services
BillingService.create_customer(user)
AnalyticsService.track_signup(user)
EmailService.send_welcome(user)
CacheService.invalidate("users")
{:ok, user}
end
end
# Adding new behavior requires modifying UserService
# Removing email feature requires modifying UserServiceRelated in General
modeling-omnistudio-epc-catalog
IncludedSalesforce Industries CME EPC product-modeling skill for Product2-based catalog creation. Use when creating EPC products, configuring product attributes, building offer bundles with Product Child Items, or reviewing EPC DataPack JSON metadata for product catalog changes. TRIGGER when: user creates or updates Product2 EPC records, AttributeAssignment payloads, AttributeMetadata/AttributeDefaultValues, Offer bundles, or ProductChildItem relationships. DO NOT TRIGGER when: designing OmniScripts/FlexCards/Integration Procedures (use building-omnistudio-omniscript, building-omnistudio-flexcard, or building-omnistudio-integration-procedure), implementing Apex business logic (use generating-apex), or troubleshooting deployment pipelines (use deploying-metadata).
relationship-science-coach
IncludedUse this skill for direct, practical adult relationship coaching: couples conflict, repair, trust, marriage, dating, flirting, attachment patterns, emotional connection, sex, desire differences, eroticism, kink negotiation, affection, love languages, breakups, and long-term passion. Draw on Gottman, EFT and Hold Me Tight, attachment science, modern sex research, Perel, Nagoski, Kerner, Schnarch, Love and Stosny, and flexible love-language tools. Be concrete and low-hedge. Redirect only for imminent danger, abuse, coercive control, minors, non-consent, self-harm, stalking, or medical/legal/psychiatric decisions.
building-sf-integrations
IncludedSalesforce integration architecture and runtime plumbing with 120-point scoring. Use this skill to set up Named Credentials, External Credentials, External Services, REST/SOAP callout patterns, Platform Events, and Change Data Capture. TRIGGER when: user sets up Named Credentials, External Services, REST/SOAP callouts, Platform Events, CDC, or touches .namedCredential-meta.xml files. DO NOT TRIGGER when: Connected App/OAuth config (use configuring-connected-apps), Apex-only logic (use generating-apex), or data import/export (use handling-sf-data).
venue-templates
IncludedAccess comprehensive LaTeX templates, formatting requirements, and submission guidelines for major scientific publication venues (Nature, Science, PLOS, IEEE, ACM), academic conferences (NeurIPS, ICML, CVPR, CHI), research posters, and grant proposals (NSF, NIH, DOE, DARPA). This skill should be used when preparing manuscripts for journal submission, conference papers, research posters, or grant proposals and need venue-specific formatting requirements and templates.
let-fate-decide
IncludedDraws the 12 Houses of the Zodiac Tarot spread to inject entropy into planning when prompts are vague, ambiguous, or casually delegated. Interprets the spread to guide next steps. Use when the user says 'let fate decide', 'YOLO', 'whatever', 'idk', or other nonchalant phrases, makes Yu-Gi-Oh references, or when you are about to arbitrarily pick between multiple reasonable approaches. Prefer over ask-questions-if-underspecified when the user's tone is casual or playful rather than precision-seeking.
net-ops
IncludedCross-platform network troubleshooting (Windows, macOS, Linux) via local or remote shell. Use for: DNS broken, can't resolve hostnames, nslookup/dig works but apps fail, NRPT, WFP, scutil, /etc/resolver, systemd-resolved, /etc/resolv.conf, NetworkManager, VPN DNS leak residue (ProtonVPN/Mullvad/WireGuard/AnyConnect), AV/firewall blocking DNS or DoH, Tailscale DNS interaction, intermittent connectivity, remote diagnostics over SSH.