module ArchSpec::Architectures
Bundled architecture presets. Each applies a set of components and rules in one call, invoked from the DSL through ArchSpec::DSL::Context#architecture:
architecture :rails
architecture :layered, layers: { ... }
Every preset accepts overrides for its directories, so you can keep the shape while pointing at your own paths. The presets are:
-
:rails: conventional MVC that keeps controller APIs out of models and services. Optionscomponents:,controller_api:,share_helpers:. -
:rails_strict::railsplus a cycle check and a concern independence check. Adds optionconcerns:. -
:vanilla_rails::railsplus empty-directory rules for the 37signals style (forbiddingapp/services,app/forms,app/policies, and more) and the concern independence check. Optionscomponents:,empty:,controller_api:,share_helpers:,concerns:. -
:layered: ordered layers that may only depend inward, with a cycle check. Optionlayers:(order matters). -
:hexagonal: ports and adapters, keeping the domain away from adapters. Optionsapplication:,domain:,ports:,adapters:. -
:clean: clean architecture layers. Optionsframeworks:,interface_adapters:,use_cases:,entities:. -
:modular_monolith: named packages with per-package allowlists and optional public APIs. Optionscomponents:(required),allow:,public:. -
:cqrs: separates commands from queries and keeps writes out of queries. Optionscommands:,queries:,read_models:,mutating_methods:. -
:event_driven: events, publishers, and subscribers. Optionsevents:,publishers:,subscribers:. -
:ruby_conventions: generic Ruby naming idioms (noget_/set_, nois_prefix), applied project-wide. Adds no components, so it composes with any other architecture. No options.
See the architecture guides for each preset in depth.
Constants
- CONTROLLER_METHODS
- DEFAULTS
-
Every option each architecture accepts, with its default. The single source of truth for
apply: option validation checks these keys, and the architecture methods receive these values merged with the caller’s. - DEFAULT_CLEAN
- DEFAULT_CONCERNS
- DEFAULT_CQRS
- DEFAULT_EVENT_DRIVEN
- DEFAULT_HEXAGONAL
- DEFAULT_LAYERED
- DEFAULT_RAILS_MVC
- MUTATING_METHODS
- VANILLA_RAILS_EMPTY
Public Instance Methods
Source
# File lib/archspec/architectures.rb, line 141 def apply(name, dsl, **options) name = architecture_name(name) defaults = DEFAULTS[name] raise Error, "unknown architecture: #{name.inspect}" unless defaults validate_options!(name, defaults, options) send(name, dsl, **defaults.merge(options)) end
Applies the named preset to dsl, forwarding options to it. Raises ArchSpec::Error for an unknown name. Called by ArchSpec::DSL::Context#architecture, so you rarely call it directly.
# File lib/archspec/architectures.rb, line 215 def clean(dsl, frameworks:, interface_adapters:, use_cases:, entities:) layered( dsl, layers: { frameworks: frameworks, interface_adapters: interface_adapters, use_cases: use_cases, entities: entities } ) end
# File lib/archspec/architectures.rb, line 244 def cqrs(dsl, commands:, queries:, read_models:, mutating_methods:) components = normalize_map(commands: commands, queries: queries) components[:read_models] = read_models if read_models define_components(dsl, components) proxy_for(dsl, :commands).cannot_use :queries proxy_for(dsl, :queries).cannot_use :commands proxy_for(dsl, :queries).cannot_call(*mutating_methods) dsl.no_cycles(among: components.keys) end
# File lib/archspec/architectures.rb, line 255 def event_driven(dsl, events:, publishers:, subscribers:) roles = normalize_map(events: events, publishers: publishers, subscribers: subscribers) define_components(dsl, roles) proxy_for(dsl, :events).cannot_use :publishers, :subscribers proxy_for(dsl, :publishers).can_only_use :events proxy_for(dsl, :subscribers).can_only_use :events dsl.no_cycles(among: roles.keys) end
# File lib/archspec/architectures.rb, line 199 def hexagonal(dsl, application:, domain:, ports:, adapters:) roles = normalize_map( application: application, domain: domain, ports: ports, adapters: adapters ) define_components(dsl, roles) proxy_for(dsl, :application).can_only_use :domain, :ports proxy_for(dsl, :domain).cannot_use :adapters proxy_for(dsl, :ports).cannot_use :adapters proxy_for(dsl, :adapters).can_only_use :application, :domain, :ports dsl.no_cycles(among: roles.keys) end
Source
# File lib/archspec/architectures.rb, line 186 def layered(dsl, layers:) ordered = normalize_map(layers) define_components(dsl, ordered) names = ordered.keys names.each_with_index do |name, index| allowed = names[(index + 1)..] || [] proxy_for(dsl, name).can_only_use(*allowed) end dsl.no_cycles(among: names) end
# File lib/archspec/architectures.rb, line 227 def modular_monolith(dsl, components:, allow: {}, public: {}) raise Error, 'architecture :modular_monolith requires the components: option' unless components components = normalize_map(components) define_components(dsl, components) components.each_key do |name| allowed = Array(allow[name] || allow[name.to_s]) proxy_for(dsl, name).can_only_use(*allowed) patterns = Array(public[name] || public[name.to_s]) proxy_for(dsl, name).public_api(*patterns) if patterns.any? end dsl.no_cycles(among: components.keys) end
# File lib/archspec/architectures.rb, line 150 def rails(dsl, components:, controller_api:, share_helpers:) components = normalize_map(components) missing = %i[controllers models] - components.keys if missing.any? raise Error, "the rails architectures need controllers and models components, missing: #{missing.join(', ')}" end define_components(dsl, components) forbidden = (share_helpers ? %i[controllers] : %i[controllers helpers]) & components.keys proxy_for(dsl, :controllers).can_only_use(*components.keys & %i[models services helpers mailers jobs]) (%i[models services] & components.keys).each do |name| proxy = proxy_for(dsl, name) proxy.cannot_use(*forbidden) proxy.cannot_call(*controller_api, receiver: :none) unless controller_api.empty? end end
# File lib/archspec/architectures.rb, line 169 def rails_strict(dsl, components:, controller_api:, share_helpers:, concerns:) components = normalize_map(components) rails(dsl, components: components, controller_api: controller_api, share_helpers: share_helpers) dsl.no_cycles(among: components.keys) independent_concerns(dsl, concerns) end
Source
# File lib/archspec/architectures.rb, line 270 def ruby_conventions(dsl) %i[instance class].each do |scope| forbid_name(dsl, /\A(get|set)_/, 'use attr_ readers and writers or plain names, not get_/set_', scope: scope) forbid_name(dsl, /\Ais_/, 'name predicates with a trailing ? and no is_ prefix (has_ is fine)', scope: scope) end end
Applies the generic Ruby naming idioms project-wide: no get_/set_ accessors and no is_ predicate prefix. Adds no components, so it composes with any other architecture. Project-specific conventions (the with_x / without_x pairing, the +supports_*?+ ban) stay opt-in through the +method_names.matching(…)+ primitives.
# File lib/archspec/architectures.rb, line 176 def vanilla_rails(dsl, components:, empty:, controller_api:, share_helpers:, concerns:) rails(dsl, components: components, controller_api: controller_api, share_helpers: share_helpers) empty.each do |name, (pattern, reason)| dsl.component(name, in: pattern).must_be_empty(because: reason) end independent_concerns(dsl, concerns) end