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(aliases:rails_mvc,:rails_way): 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(alias:rails_layered): ordered layers that may only depend inward, with a cycle check. Optionlayers:(order matters). -
:hexagonal(alias:rails_hexagonal): ports and adapters, keeping the domain away from adapters. Optionsapplication:,domain:,ports:,adapters:. -
:clean(alias:rails_clean): clean architecture layers. Optionsframeworks:,interface_adapters:,use_cases:,entities:. -
:modular_monolith(alias:bounded_contexts): named packages with per-package allowlists and optional public APIs. Optionscomponents:(required),allow:,public:. -
:cqrs(alias:rails_cqrs): separates commands from queries and keeps writes out of queries. Optionscommands:,queries:,read_models:,mutating_methods:. -
:event_driven(alias:rails_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 guides at archspecrb.dev/architectures/ for each in depth.
Constants
- CONTROLLER_METHODS
- 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 112 def apply(name, dsl, **options) case name.to_sym when :rails, :rails_mvc, :rails_way rails_mvc( dsl, components: options.fetch(:components, DEFAULT_RAILS_MVC), controller_api: options.fetch(:controller_api, CONTROLLER_METHODS), share_helpers: options.fetch(:share_helpers, false) ) when :rails_strict rails_strict( dsl, components: options.fetch(:components, DEFAULT_RAILS_MVC), controller_api: options.fetch(:controller_api, CONTROLLER_METHODS), share_helpers: options.fetch(:share_helpers, false), concerns: options.fetch(:concerns, DEFAULT_CONCERNS) ) when :vanilla_rails vanilla_rails( dsl, components: options.fetch(:components, DEFAULT_RAILS_MVC), empty: options.fetch(:empty, VANILLA_RAILS_EMPTY), controller_api: options.fetch(:controller_api, CONTROLLER_METHODS), share_helpers: options.fetch(:share_helpers, false), concerns: options.fetch(:concerns, DEFAULT_CONCERNS) ) when :layered, :rails_layered layered(dsl, layers: options.fetch(:layers, DEFAULT_LAYERED)) when :hexagonal, :rails_hexagonal hexagonal(dsl, **with_defaults(DEFAULT_HEXAGONAL, options)) when :clean, :rails_clean clean(dsl, **with_defaults(DEFAULT_CLEAN, options)) when :modular_monolith, :bounded_contexts modular_monolith( dsl, components: options.fetch(:components), allow: options.fetch(:allow, {}), public: options.fetch(:public, {}) ) when :cqrs, :rails_cqrs cqrs(dsl, **with_defaults(DEFAULT_CQRS, options)) when :event_driven, :rails_event_driven event_driven(dsl, **with_defaults(DEFAULT_EVENT_DRIVEN, options)) when :ruby_conventions ruby_conventions(dsl) else raise Error, "Unknown ArchSpec architecture: #{name.inspect}" end 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 224 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 251 def cqrs(dsl, commands:, queries:, read_models: nil, mutating_methods: 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 262 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_use :events proxy_for(dsl, :subscribers).can_use :events dsl.no_cycles!(among: roles.keys) end
# File lib/archspec/architectures.rb, line 208 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_use :domain, :ports proxy_for(dsl, :domain).cannot_use :adapters proxy_for(dsl, :ports).cannot_use :adapters proxy_for(dsl, :adapters).can_use :application, :domain, :ports dsl.no_cycles!(among: roles.keys) end
Source
# File lib/archspec/architectures.rb, line 195 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_use(*allowed) end dsl.no_cycles!(among: names) end
# File lib/archspec/architectures.rb, line 236 def modular_monolith(dsl, components:, allow: {}, public: {}) 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_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 162 def rails_mvc(dsl, components:, controller_api: CONTROLLER_METHODS, share_helpers: false) components = normalize_map(components) define_components(dsl, components) forbidden = share_helpers ? %i[controllers] : %i[controllers helpers] proxy_for(dsl, :controllers).can_use(*components.keys & %i[models services helpers mailers jobs]) proxy_for(dsl, :models).cannot_use(*components.keys & forbidden) proxy_for(dsl, :services).cannot_use(*components.keys & forbidden) return if controller_api.empty? proxy_for(dsl, :models).cannot_call(*controller_api, receiver: :none) proxy_for(dsl, :services).cannot_call(*controller_api, receiver: :none) end
# File lib/archspec/architectures.rb, line 177 def rails_strict(dsl, components:, controller_api: CONTROLLER_METHODS, share_helpers: false, concerns: DEFAULT_CONCERNS) components = normalize_map(components) rails_mvc(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 277 def ruby_conventions(dsl) forbid_name(dsl, /\A(get|set)_/, 'use attr_ readers and writers or plain names, not get_/set_') forbid_name(dsl, /\Ais_/, 'name predicates with a trailing ? and no is_ prefix (has_ is fine)') 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 +methods.matching(…)+ primitives.
# File lib/archspec/architectures.rb, line 184 def vanilla_rails(dsl, components:, empty:, controller_api: CONTROLLER_METHODS, share_helpers: false, concerns: DEFAULT_CONCERNS) rails_mvc(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