Skip to content

Commit 189a755

Browse files
authored
Renaming architecture to layer (#53)
Adding a new `layer` violation type, which replaces the now deprecated `architecture` violation type
1 parent 1ad792d commit 189a755

File tree

16 files changed

+591
-172
lines changed

16 files changed

+591
-172
lines changed

Gemfile.lock

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ GIT
1717
PATH
1818
remote: .
1919
specs:
20-
packwerk-extensions (0.1.11)
20+
packwerk-extensions (0.2.0)
2121
packwerk (>= 2.2.1)
2222
railties (>= 6.0.0)
2323
sorbet-runtime

README.md

Lines changed: 28 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ Currently, it ships the following checkers to help improve the boundaries betwee
66
- A `privacy` checker that ensures other packages are using your package's public API
77
- A `visibility` checker that allows packages to be private except to an explicit group of other packages.
88
- A `folder_visibility` checker that allows packages to their sibling packs and parent pack (to be used in an application that uses folder packs)
9-
- An `architecture` checker that allows packages to specify their "layer" and requires that each layer only communicate with layers below it.
9+
- A `layer` (formerly `architecture`) checker that allows packages to specify their "layer" and requires that each layer only communicate with layers below it.
1010

1111
## Installation
1212

@@ -26,7 +26,7 @@ require:
2626
- packwerk/privacy/checker
2727
- packwerk/visibility/checker
2828
- packwerk/folder_visibility/checker
29-
- packwerk/architecture/checker
29+
- packwerk/layer/checker
3030
```
3131
3232
## Privacy Checker
@@ -194,25 +194,46 @@ packs/b/packs/h OK (sibling)
194194
packs/c VIOLATION
195195
```
196196
197-
## Architecture Checker
198-
The architecture checker can be used to enforce constraints on what can depend on what.
197+
## Layer Checker
198+
The layer checker can be used to enforce constraints on what can depend on what.
199199
200-
To enforce architecture for your package, first define the `architecture_layers` in `packwerk.yml`, for example:
200+
To enforce layers for your package, first define the `layers` in `packwerk.yml`, for example:
201201
```
202-
architecture_layers:
202+
layers:
203203
- package
204204
- utility
205205
```
206206
207207
Then, turn on the checker in your package:
208208
```yaml
209209
# components/merchandising/package.yml
210-
enforce_architecture: true
210+
enforce_layers: true
211211
layer: utility
212212
```
213213

214214
Now this pack can only depend on other utility packages.
215215

216+
### Deprecated Architecture Checker
217+
The "Layer Checker" was formerly named "Architecture Checker". The associated keys were:
218+
- packwerk.yml `architecture_layers`, which is now `layers`
219+
- package.yml `enforce_architecture`, which is now `enforce_layers`
220+
- package.yml `layer` is still a valid key
221+
- package_todo.yml - `architecture`, which is now `layer`
222+
223+
```bash
224+
# script to migrate code from deprecated "architecture" violations to "layer" violations
225+
# sed and ripgrep required
226+
227+
# replace 'architecture_layers' with 'layers' in packwerk.yml
228+
sed -i '' 's/architecture_layers/layers/g' ./packwerk.yml
229+
230+
# replace 'enforce_architecture' with 'enforce_layers' in package.yml files
231+
`rg -l 'enforce_architecture' -g 'package.yml' | xargs sed -i '' 's,enforce_architecture,enforce_layers,g'`
232+
233+
# replace '- architecture' with '- layer' in package_todo.yml files
234+
`rg -l 'architecture' -g 'package_todo.yml' | xargs sed -i '' 's/- architecture/- layer/g'`
235+
```
236+
216237

217238
## Contributing
218239

lib/packwerk-extensions.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
require 'packwerk/privacy/checker'
88
require 'packwerk/visibility/checker'
99
require 'packwerk/folder_visibility/checker'
10-
require 'packwerk/architecture/checker'
10+
require 'packwerk/layer/checker'
1111

1212
module Packwerk
1313
module Extensions
Lines changed: 22 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,25 @@
11
# typed: strict
22
# frozen_string_literal: true
33

4-
require 'packwerk/architecture/layers'
5-
require 'packwerk/architecture/package'
6-
require 'packwerk/architecture/validator'
4+
require 'packwerk/layer/config'
5+
require 'packwerk/layer/layers'
6+
require 'packwerk/layer/package'
7+
require 'packwerk/layer/validator'
78

89
module Packwerk
9-
module Architecture
10+
module Layer
1011
# This enforces "layered architecture," which allows each class to be designated as one of N layers
1112
# configured by the client in `packwerk.yml`, for example:
1213
#
13-
# architecture_layers:
14+
# layers:
1415
# - orchestrator
1516
# - business_domain
1617
# - platform
1718
# - utility
1819
# - specification
1920
#
2021
# Then a package can configure:
21-
# enforce_architecture: true | false | strict
22+
# enforce_layers: true | false | strict
2223
# layer: utility
2324
#
2425
# This is intended to provide:
@@ -30,11 +31,14 @@ class Checker
3031
extend T::Sig
3132
include Packwerk::Checker
3233

33-
VIOLATION_TYPE = T.let('architecture', String)
34+
sig { void }
35+
def initialize
36+
@violation_type = T.let(@violation_type, T.nilable(String))
37+
end
3438

3539
sig { override.returns(String) }
3640
def violation_type
37-
VIOLATION_TYPE
41+
@violation_type ||= layer_config.violation_key
3842
end
3943

4044
sig do
@@ -55,7 +59,7 @@ def invalid_reference?(reference)
5559
end
5660
def strict_mode_violation?(listed_offense)
5761
constant_package = listed_offense.reference.package
58-
constant_package.config['enforce_architecture'] == 'strict'
62+
constant_package.config[layer_config.enforce_key] == 'strict'
5963
end
6064

6165
sig do
@@ -68,9 +72,9 @@ def message(reference)
6872
referencing_package = Package.from(reference.package, layers)
6973

7074
message = <<~MESSAGE
71-
Architecture layer violation: '#{reference.constant.name}' belongs to '#{reference.constant.package}', whose architecture layer type is "#{constant_package.layer}."
72-
This constant cannot be referenced by '#{reference.package}', whose architecture layer type is "#{referencing_package.layer}."
73-
Packs in a lower layer may not access packs in a higher layer. See the `architecture_layers` in packwerk.yml. Current hierarchy:
75+
Layer violation: '#{reference.constant.name}' belongs to '#{reference.constant.package}', whose layer type is "#{constant_package.layer}".
76+
This constant cannot be referenced by '#{reference.package}', whose layer type is "#{referencing_package.layer}".
77+
Packs in a lower layer may not access packs in a higher layer. See the `layers` in packwerk.yml. Current hierarchy:
7478
- #{layers.names_list.join("\n- ")}
7579
7680
#{standard_help_message(reference)}
@@ -92,7 +96,12 @@ def standard_help_message(reference)
9296

9397
sig { returns(Layers) }
9498
def layers
95-
@layers ||= T.let(Layers.new, T.nilable(Packwerk::Architecture::Layers))
99+
@layers ||= T.let(Layers.new, T.nilable(Packwerk::Layer::Layers))
100+
end
101+
102+
sig { returns(Config) }
103+
def layer_config
104+
@layer_config ||= T.let(Config.new, T.nilable(Config))
96105
end
97106
end
98107
end

lib/packwerk/layer/config.rb

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
# typed: strict
2+
# frozen_string_literal: true
3+
4+
module Packwerk
5+
module Layer
6+
class Config
7+
extend T::Sig
8+
9+
ARCHITECTURE_VIOLATION_TYPE = T.let('architecture', String)
10+
ARCHITECTURE_ENFORCE = T.let('enforce_architecture', String)
11+
LAYER_VIOLATION_TYPE = T.let('layer', String)
12+
LAYER_ENFORCE = T.let('enforce_layers', String)
13+
14+
sig { void }
15+
def initialize
16+
@layers_key_configured = T.let(@layers_key_configured, T.nilable(T::Boolean))
17+
@layers_list = T.let(@layers_list, T.nilable(T::Array[String]))
18+
end
19+
20+
sig { returns(T::Array[String]) }
21+
def layers_list
22+
@layers_list ||= YAML.load_file('packwerk.yml')[layers_key] || []
23+
end
24+
25+
sig { returns(T::Boolean) }
26+
def layers_key_configured?
27+
@layers_key_configured ||= YAML.load_file('packwerk.yml')['architecture_layers'].nil?
28+
end
29+
30+
sig { returns(String) }
31+
def layers_key
32+
layers_key_configured? ? 'layers' : 'architecture_layers'
33+
end
34+
35+
sig { returns(String) }
36+
def violation_key
37+
layers_key_configured? ? LAYER_VIOLATION_TYPE : ARCHITECTURE_VIOLATION_TYPE
38+
end
39+
40+
sig { returns(String) }
41+
def enforce_key
42+
layers_key_configured? ? LAYER_ENFORCE : ARCHITECTURE_ENFORCE
43+
end
44+
end
45+
end
46+
end
Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
# frozen_string_literal: true
33

44
module Packwerk
5-
module Architecture
5+
module Layer
66
class Layers
77
extend T::Sig
88

@@ -29,7 +29,7 @@ def names
2929

3030
sig { returns(T::Array[String]) }
3131
def names_list
32-
@names_list ||= YAML.load_file('packwerk.yml')['architecture_layers'] || []
32+
@names_list ||= Config.new.layers_list
3333
end
3434
end
3535
end
Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
# frozen_string_literal: true
33

44
module Packwerk
5-
module Architecture
5+
module Layer
66
class Package < T::Struct
77
extend T::Sig
88

@@ -46,7 +46,7 @@ def from(package, layers)
4646

4747
Package.new(
4848
layer: layer,
49-
enforcement_setting: config['enforce_architecture'],
49+
enforcement_setting: config[Config.new.enforce_key],
5050
config: config
5151
)
5252
end
Lines changed: 40 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
# frozen_string_literal: true
33

44
module Packwerk
5-
module Architecture
5+
module Layer
66
class Validator
77
extend T::Sig
88
include Packwerk::Validator
@@ -16,13 +16,17 @@ def call(package_set, configuration)
1616
package_set.each do |package|
1717
config = package.config
1818
f = Pathname.new(package.name).join('package.yml').to_s
19+
package = Package.from(package, layers)
20+
1921
next if !config
2022

21-
result = check_enforce_architecture_setting(f, config['enforce_architecture'])
23+
result = check_enforce_key(package, f, config)
2224
results << result
2325
next if !result.ok?
2426

25-
package = Package.from(package, layers)
27+
result = check_enforce_layers_setting(f, config[layer_config.enforce_key])
28+
results << result
29+
next if !result.ok?
2630

2731
result = check_layer_setting(package, f)
2832
results << result
@@ -34,12 +38,39 @@ def call(package_set, configuration)
3438

3539
sig { returns(Layers) }
3640
def layers
37-
@layers ||= T.let(Layers.new, T.nilable(Packwerk::Architecture::Layers))
41+
@layers ||= T.let(Layers.new, T.nilable(Packwerk::Layer::Layers))
42+
end
43+
44+
sig { returns(Config) }
45+
def layer_config
46+
@layer_config ||= T.let(Config.new, T.nilable(Config))
3847
end
3948

4049
sig { override.returns(T::Array[String]) }
4150
def permitted_keys
42-
%w[enforce_architecture layer]
51+
[layer_config.enforce_key, 'layer']
52+
end
53+
54+
sig do
55+
params(package: Package, config_file_path: String, config: T::Hash[T.untyped, T.untyped]).returns(Result)
56+
end
57+
def check_enforce_key(package, config_file_path, config)
58+
enforce_layer_present = !config[Config::LAYER_ENFORCE].nil?
59+
enforce_architecture_present = !config[Config::ARCHITECTURE_ENFORCE].nil?
60+
61+
if layer_config.enforce_key == Config::LAYER_ENFORCE && enforce_architecture_present
62+
Result.new(
63+
ok: false,
64+
error_value: "Unexpected `enforce_architecture` option in #{config_file_path.inspect}. Did you mean `enforce_layers`?"
65+
)
66+
elsif layer_config.enforce_key == Config::ARCHITECTURE_ENFORCE && enforce_layer_present
67+
Result.new(
68+
ok: false,
69+
error_value: "Unexpected `enforce_layers` option in #{config_file_path.inspect}. Did you mean `enforce_architecture`?"
70+
)
71+
else
72+
Result.new(ok: true)
73+
end
4374
end
4475

4576
sig do
@@ -52,7 +83,7 @@ def check_layer_setting(package, config_file_path)
5283
if layer.nil? && package.enforces?
5384
Result.new(
5485
ok: false,
55-
error_value: "Invalid 'layer' option in #{config_file_path.inspect}: #{package.layer.inspect}. `layer` must be set if `enforce_architecture` is on."
86+
error_value: "Invalid 'layer' option in #{config_file_path.inspect}: #{package.layer.inspect}. `layer` must be set if `#{layer_config.enforce_key}` is on."
5687
)
5788
elsif valid_layer
5889
Result.new(ok: true)
@@ -67,19 +98,19 @@ def check_layer_setting(package, config_file_path)
6798
sig do
6899
params(config_file_path: String, setting: T.untyped).returns(Result)
69100
end
70-
def check_enforce_architecture_setting(config_file_path, setting)
101+
def check_enforce_layers_setting(config_file_path, setting)
71102
activated_value = [true, 'strict'].include?(setting)
72103
valid_value = [true, nil, false, 'strict'].include?(setting)
73104
layers_set = layers.names.any?
74105
if !valid_value
75106
Result.new(
76107
ok: false,
77-
error_value: "Invalid 'enforce_architecture' option in #{config_file_path.inspect}: #{setting.inspect}"
108+
error_value: "Invalid '#{layer_config.enforce_key}' option in #{config_file_path.inspect}: #{setting.inspect}"
78109
)
79110
elsif activated_value && !layers_set
80111
Result.new(
81112
ok: false,
82-
error_value: "Cannot set 'enforce_architecture' option in #{config_file_path.inspect} until `architectural_layers` have been specified in `packwerk.yml`"
113+
error_value: "Cannot set '#{layer_config.enforce_key}' option in #{config_file_path.inspect} until `layers` have been specified in `packwerk.yml`"
83114
)
84115
else
85116
Result.new(ok: true)

lib/packwerk/visibility/validator.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ def call(package_set, configuration)
1414
visible_settings = package_manifests_settings_for(configuration, 'visible_to')
1515
results = T.let([], T::Array[Result])
1616

17-
all_package_names = package_set.map(&:name).to_set
17+
all_package_names = package_set.to_set(&:name)
1818

1919
package_manifests_settings_for(configuration, 'enforce_visibility').each do |config, setting|
2020
next if setting.nil?

packwerk-extensions.gemspec

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
Gem::Specification.new do |spec|
22
spec.name = 'packwerk-extensions'
3-
spec.version = '0.1.11'
3+
spec.version = '0.2.0'
44
spec.authors = ['Gusto Engineers']
55
spec.email = ['[email protected]']
66

@@ -19,7 +19,7 @@ Gem::Specification.new do |spec|
1919
'public gem pushes.'
2020
end
2121

22-
spec.required_ruby_version = Gem::Requirement.new('>= 2.6.0')
22+
spec.required_ruby_version = Gem::Requirement.new('>= 2.7')
2323
# Specify which files should be added to the gem when it is released.
2424
spec.files = Dir['README.md', 'lib/**/*']
2525

0 commit comments

Comments
 (0)