modwire 3.0.0__tar.gz → 3.1.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {modwire-3.0.0 → modwire-3.1.1}/.github/workflows/release.yml +11 -1
- {modwire-3.0.0 → modwire-3.1.1}/PKG-INFO +24 -1
- {modwire-3.0.0 → modwire-3.1.1}/README.md +22 -0
- {modwire-3.0.0 → modwire-3.1.1}/pyproject.toml +7 -0
- {modwire-3.0.0 → modwire-3.1.1}/src/modwire/_version.py +3 -3
- modwire-3.1.1/src/modwire/modules/__init__.py +3 -0
- modwire-3.1.1/src/modwire/modules/generate.py +74 -0
- modwire-3.1.1/src/modwire/modules/scaffoldings/hexagonal/copier.yml +38 -0
- modwire-3.1.1/src/modwire/modules/scaffoldings/hexagonal/template/php/src/Application/{{ command_name }}.php.jinja +14 -0
- modwire-3.1.1/src/modwire/modules/scaffoldings/hexagonal/template/php/src/Application/{{ service_name }}.php.jinja +24 -0
- modwire-3.1.1/src/modwire/modules/scaffoldings/hexagonal/template/php/src/Domain/{{ aggregate_name }}.php.jinja +27 -0
- modwire-3.1.1/src/modwire/modules/scaffoldings/hexagonal/template/php/src/Infrastructure/InMemory{{ repository_name }}.php.jinja +24 -0
- modwire-3.1.1/src/modwire/modules/scaffoldings/hexagonal/template/php/src/Ports/{{ repository_name }}.php.jinja +14 -0
- modwire-3.1.1/src/modwire/modules/scaffoldings/hexagonal/template/php/src/Ui/{{ controller_name }}.php.jinja +27 -0
- modwire-3.1.1/src/modwire/modules/scaffoldings/hexagonal/template/python/{{ module_name }}/__init__.py.jinja +5 -0
- modwire-3.1.1/src/modwire/modules/scaffoldings/hexagonal/template/python/{{ module_name }}/application.py.jinja +22 -0
- modwire-3.1.1/src/modwire/modules/scaffoldings/hexagonal/template/python/{{ module_name }}/domain.py.jinja +20 -0
- modwire-3.1.1/src/modwire/modules/scaffoldings/hexagonal/template/python/{{ module_name }}/infrastructure.py.jinja +17 -0
- modwire-3.1.1/src/modwire/modules/scaffoldings/hexagonal/template/python/{{ module_name }}/ports.py.jinja +16 -0
- modwire-3.1.1/src/modwire/modules/scaffoldings/hexagonal/template/python/{{ module_name }}/ui.py.jinja +21 -0
- modwire-3.1.1/src/modwire/modules/scaffoldings/hexagonal/template/typescript/{{ module_name }}/application.ts.jinja +20 -0
- modwire-3.1.1/src/modwire/modules/scaffoldings/hexagonal/template/typescript/{{ module_name }}/domain.ts.jinja +18 -0
- modwire-3.1.1/src/modwire/modules/scaffoldings/hexagonal/template/typescript/{{ module_name }}/infrastructure.ts.jinja +14 -0
- modwire-3.1.1/src/modwire/modules/scaffoldings/hexagonal/template/typescript/{{ module_name }}/ports.ts.jinja +6 -0
- modwire-3.1.1/src/modwire/modules/scaffoldings/hexagonal/template/typescript/{{ module_name }}/ui.ts.jinja +19 -0
- modwire-3.1.1/src/modwire/modules/scaffoldings/layered/copier.yml +23 -0
- modwire-3.1.1/src/modwire/modules/scaffoldings/layered/template/{{ module_name }}/application.py.jinja +18 -0
- modwire-3.1.1/src/modwire/modules/scaffoldings/layered/template/{{ module_name }}/domain.py.jinja +12 -0
- modwire-3.1.1/src/modwire/modules/scaffoldings/layered/template/{{ module_name }}/infrastructure.py.jinja +11 -0
- modwire-3.1.1/src/modwire/modules/scaffoldings/layered/template/{{ module_name }}/ui.py.jinja +20 -0
- {modwire-3.0.0 → modwire-3.1.1}/src/modwire.egg-info/PKG-INFO +24 -1
- {modwire-3.0.0 → modwire-3.1.1}/src/modwire.egg-info/SOURCES.txt +27 -3
- {modwire-3.0.0 → modwire-3.1.1}/src/modwire.egg-info/requires.txt +1 -0
- modwire-3.1.1/tests/modules/test_generate.py +56 -0
- {modwire-3.0.0 → modwire-3.1.1}/uv.lock +264 -0
- modwire-3.0.0/src/modwire.egg-info/scm_file_list.json +0 -63
- modwire-3.0.0/src/modwire.egg-info/scm_version.json +0 -8
- {modwire-3.0.0 → modwire-3.1.1}/.github/ISSUE_TEMPLATE/bug_report.yml +0 -0
- {modwire-3.0.0 → modwire-3.1.1}/.github/ISSUE_TEMPLATE/config.yml +0 -0
- {modwire-3.0.0 → modwire-3.1.1}/.github/ISSUE_TEMPLATE/feature_request.yml +0 -0
- {modwire-3.0.0 → modwire-3.1.1}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
- {modwire-3.0.0 → modwire-3.1.1}/.github/workflows/ci.yml +0 -0
- {modwire-3.0.0 → modwire-3.1.1}/.gitignore +0 -0
- {modwire-3.0.0 → modwire-3.1.1}/CHANGELOG.md +0 -0
- {modwire-3.0.0 → modwire-3.1.1}/CONTRIBUTING.md +0 -0
- {modwire-3.0.0 → modwire-3.1.1}/LICENSE +0 -0
- {modwire-3.0.0 → modwire-3.1.1}/docs/wiki/Development-checks.md +0 -0
- {modwire-3.0.0 → modwire-3.1.1}/docs/wiki/Home.md +0 -0
- {modwire-3.0.0 → modwire-3.1.1}/docs/wiki/Reporting-bugs.md +0 -0
- {modwire-3.0.0 → modwire-3.1.1}/docs/wiki/Requesting-features.md +0 -0
- {modwire-3.0.0 → modwire-3.1.1}/setup.cfg +0 -0
- {modwire-3.0.0 → modwire-3.1.1}/src/modwire/__init__.py +0 -0
- {modwire-3.0.0 → modwire-3.1.1}/src/modwire/architecture/__init__.py +0 -0
- {modwire-3.0.0 → modwire-3.1.1}/src/modwire/architecture/boundaries/__init__.py +0 -0
- {modwire-3.0.0 → modwire-3.1.1}/src/modwire/architecture/boundaries/analyzers/__init__.py +0 -0
- {modwire-3.0.0 → modwire-3.1.1}/src/modwire/architecture/boundaries/analyzers/backward.py +0 -0
- {modwire-3.0.0 → modwire-3.1.1}/src/modwire/architecture/boundaries/analyzers/no_cycles.py +0 -0
- {modwire-3.0.0 → modwire-3.1.1}/src/modwire/architecture/boundaries/analyzers/no_reentry.py +0 -0
- {modwire-3.0.0 → modwire-3.1.1}/src/modwire/architecture/boundaries/base.py +0 -0
- {modwire-3.0.0 → modwire-3.1.1}/src/modwire/architecture/boundaries/config.py +0 -0
- {modwire-3.0.0 → modwire-3.1.1}/src/modwire/architecture/boundaries/map.py +0 -0
- {modwire-3.0.0 → modwire-3.1.1}/src/modwire/architecture/boundaries/pipeline.py +0 -0
- {modwire-3.0.0 → modwire-3.1.1}/src/modwire/architecture/boundaries/tags/__init__.py +0 -0
- {modwire-3.0.0 → modwire-3.1.1}/src/modwire/architecture/boundaries/tags/matcher.py +0 -0
- {modwire-3.0.0 → modwire-3.1.1}/src/modwire/architecture/boundaries/tags/tag_map.py +0 -0
- {modwire-3.0.0 → modwire-3.1.1}/src/modwire/architecture/config.py +0 -0
- {modwire-3.0.0 → modwire-3.1.1}/src/modwire/architecture/insight/__init__.py +0 -0
- {modwire-3.0.0 → modwire-3.1.1}/src/modwire/architecture/insight/base.py +0 -0
- {modwire-3.0.0 → modwire-3.1.1}/src/modwire/architecture/insight/pipeline.py +0 -0
- {modwire-3.0.0 → modwire-3.1.1}/src/modwire/architecture/insight/reports/__init__.py +0 -0
- {modwire-3.0.0 → modwire-3.1.1}/src/modwire/architecture/insight/reports/callables.py +0 -0
- {modwire-3.0.0 → modwire-3.1.1}/src/modwire/architecture/insight/reports/clusters.py +0 -0
- {modwire-3.0.0 → modwire-3.1.1}/src/modwire/architecture/insight/reports/coherence.py +0 -0
- {modwire-3.0.0 → modwire-3.1.1}/src/modwire/architecture/insight/reports/exports.py +0 -0
- {modwire-3.0.0 → modwire-3.1.1}/src/modwire/architecture/insight/reports/hotspots.py +0 -0
- {modwire-3.0.0 → modwire-3.1.1}/src/modwire/architecture/report.py +0 -0
- {modwire-3.0.0 → modwire-3.1.1}/src/modwire/architecture/root.py +0 -0
- {modwire-3.0.0 → modwire-3.1.1}/src/modwire/architecture/shape/__init__.py +0 -0
- {modwire-3.0.0 → modwire-3.1.1}/src/modwire/architecture/shape/base.py +0 -0
- {modwire-3.0.0 → modwire-3.1.1}/src/modwire/architecture/shape/config.py +0 -0
- {modwire-3.0.0 → modwire-3.1.1}/src/modwire/architecture/shape/pipeline.py +0 -0
- {modwire-3.0.0 → modwire-3.1.1}/src/modwire/architecture/shape/rules/__init__.py +0 -0
- {modwire-3.0.0 → modwire-3.1.1}/src/modwire/architecture/shape/rules/abstract_class_resolver.py +0 -0
- {modwire-3.0.0 → modwire-3.1.1}/src/modwire/architecture/shape/rules/callable_resolver.py +0 -0
- {modwire-3.0.0 → modwire-3.1.1}/src/modwire/architecture/shape/rules/class_resolver.py +0 -0
- {modwire-3.0.0 → modwire-3.1.1}/src/modwire/architecture/shape/rules/file_resolver.py +0 -0
- {modwire-3.0.0 → modwire-3.1.1}/src/modwire/architecture/shape/rules/import_resolver.py +0 -0
- {modwire-3.0.0 → modwire-3.1.1}/src/modwire/architecture/shape/rules/property_resolver.py +0 -0
- {modwire-3.0.0 → modwire-3.1.1}/src/modwire/architecture/shape/rules/signature_resolver.py +0 -0
- {modwire-3.0.0 → modwire-3.1.1}/src/modwire/architecture/shape/rules/symbol_resolver.py +0 -0
- {modwire-3.0.0 → modwire-3.1.1}/src/modwire.egg-info/dependency_links.txt +0 -0
- {modwire-3.0.0 → modwire-3.1.1}/src/modwire.egg-info/top_level.txt +0 -0
- {modwire-3.0.0 → modwire-3.1.1}/tests/architecture/boundaries/test_map.py +0 -0
- {modwire-3.0.0 → modwire-3.1.1}/tests/architecture/boundaries/test_pipeline.py +0 -0
- {modwire-3.0.0 → modwire-3.1.1}/tests/architecture/test_report.py +0 -0
|
@@ -46,6 +46,15 @@ jobs:
|
|
|
46
46
|
exit 1
|
|
47
47
|
fi
|
|
48
48
|
|
|
49
|
+
- name: Use release tag as package version
|
|
50
|
+
if: github.event_name == 'release'
|
|
51
|
+
run: |
|
|
52
|
+
tag_name="${{ github.event.release.tag_name }}"
|
|
53
|
+
release_version="${tag_name#v}"
|
|
54
|
+
|
|
55
|
+
echo "MODWIRE_RELEASE_VERSION=$release_version" >> "$GITHUB_ENV"
|
|
56
|
+
echo "SETUPTOOLS_SCM_PRETEND_VERSION_FOR_MODWIRE=$release_version" >> "$GITHUB_ENV"
|
|
57
|
+
|
|
49
58
|
- name: Set up Python
|
|
50
59
|
uses: actions/setup-python@v5
|
|
51
60
|
with:
|
|
@@ -90,12 +99,13 @@ jobs:
|
|
|
90
99
|
if: github.event_name == 'release'
|
|
91
100
|
run: |
|
|
92
101
|
python - <<'PY'
|
|
102
|
+
import os
|
|
93
103
|
import sys
|
|
94
104
|
import zipfile
|
|
95
105
|
from email.parser import Parser
|
|
96
106
|
from pathlib import Path
|
|
97
107
|
|
|
98
|
-
expected_version =
|
|
108
|
+
expected_version = os.environ["MODWIRE_RELEASE_VERSION"]
|
|
99
109
|
wheels = sorted(Path("dist").glob("*.whl"))
|
|
100
110
|
versions: dict[str, str] = {}
|
|
101
111
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: modwire
|
|
3
|
-
Version: 3.
|
|
3
|
+
Version: 3.1.1
|
|
4
4
|
Summary: Map architecture boundaries and analyze extracted code maps.
|
|
5
5
|
Author: Tomasz Szpak
|
|
6
6
|
License-Expression: MIT
|
|
@@ -23,6 +23,7 @@ Classifier: Topic :: Software Development :: Quality Assurance
|
|
|
23
23
|
Requires-Python: >=3.11
|
|
24
24
|
Description-Content-Type: text/markdown
|
|
25
25
|
License-File: LICENSE
|
|
26
|
+
Requires-Dist: copier>=9.16.0
|
|
26
27
|
Requires-Dist: modwire-extraction>=1.0.1
|
|
27
28
|
Requires-Dist: pydantic>=2.8
|
|
28
29
|
Provides-Extra: dev
|
|
@@ -113,6 +114,28 @@ Reports are Pydantic models, so they can be serialized with `model_dump()` or
|
|
|
113
114
|
payload = report.model_dump(mode="json")
|
|
114
115
|
```
|
|
115
116
|
|
|
117
|
+
## Module Generation
|
|
118
|
+
|
|
119
|
+
`modwire.modules` can generate a module from any Copier template. A caller only
|
|
120
|
+
needs to create a normal Copier template directory and pass it to
|
|
121
|
+
`generate_module`.
|
|
122
|
+
|
|
123
|
+
```python
|
|
124
|
+
from pathlib import Path
|
|
125
|
+
|
|
126
|
+
from modwire.modules import generate_module
|
|
127
|
+
|
|
128
|
+
generate_module(
|
|
129
|
+
"billing",
|
|
130
|
+
Path("src/features"),
|
|
131
|
+
Path("templates/modwire-module"),
|
|
132
|
+
data={"service_name": "BillingService"},
|
|
133
|
+
)
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
If no template path is provided, `generate_module` uses the bundled `layered`
|
|
137
|
+
scaffolding. Bundled scaffoldings are packaged with the distribution wheel.
|
|
138
|
+
|
|
116
139
|
## Configuration
|
|
117
140
|
|
|
118
141
|
Boundary tags classify source IDs. Flow rules then use those tags to detect
|
|
@@ -79,6 +79,28 @@ Reports are Pydantic models, so they can be serialized with `model_dump()` or
|
|
|
79
79
|
payload = report.model_dump(mode="json")
|
|
80
80
|
```
|
|
81
81
|
|
|
82
|
+
## Module Generation
|
|
83
|
+
|
|
84
|
+
`modwire.modules` can generate a module from any Copier template. A caller only
|
|
85
|
+
needs to create a normal Copier template directory and pass it to
|
|
86
|
+
`generate_module`.
|
|
87
|
+
|
|
88
|
+
```python
|
|
89
|
+
from pathlib import Path
|
|
90
|
+
|
|
91
|
+
from modwire.modules import generate_module
|
|
92
|
+
|
|
93
|
+
generate_module(
|
|
94
|
+
"billing",
|
|
95
|
+
Path("src/features"),
|
|
96
|
+
Path("templates/modwire-module"),
|
|
97
|
+
data={"service_name": "BillingService"},
|
|
98
|
+
)
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
If no template path is provided, `generate_module` uses the bundled `layered`
|
|
102
|
+
scaffolding. Bundled scaffoldings are packaged with the distribution wheel.
|
|
103
|
+
|
|
82
104
|
## Configuration
|
|
83
105
|
|
|
84
106
|
Boundary tags classify source IDs. Flow rules then use those tags to detect
|
|
@@ -33,6 +33,7 @@ classifiers = [
|
|
|
33
33
|
"Topic :: Software Development :: Quality Assurance",
|
|
34
34
|
]
|
|
35
35
|
dependencies = [
|
|
36
|
+
"copier>=9.16.0",
|
|
36
37
|
"modwire-extraction>=1.0.1",
|
|
37
38
|
"pydantic>=2.8",
|
|
38
39
|
]
|
|
@@ -72,6 +73,12 @@ cache-dir = ".dev/ruff_cache"
|
|
|
72
73
|
package-dir = {"" = "src"}
|
|
73
74
|
include-package-data = true
|
|
74
75
|
|
|
76
|
+
[tool.setuptools.package-data]
|
|
77
|
+
"modwire.modules" = [
|
|
78
|
+
"scaffoldings/*/copier.yml",
|
|
79
|
+
"scaffoldings/*/template/**/*.jinja",
|
|
80
|
+
]
|
|
81
|
+
|
|
75
82
|
[tool.setuptools.packages.find]
|
|
76
83
|
where = ["src"]
|
|
77
84
|
|
|
@@ -18,7 +18,7 @@ version_tuple: tuple[int | str, ...]
|
|
|
18
18
|
commit_id: str | None
|
|
19
19
|
__commit_id__: str | None
|
|
20
20
|
|
|
21
|
-
__version__ = version = '3.
|
|
22
|
-
__version_tuple__ = version_tuple = (3,
|
|
21
|
+
__version__ = version = '3.1.1'
|
|
22
|
+
__version_tuple__ = version_tuple = (3, 1, 1)
|
|
23
23
|
|
|
24
|
-
__commit_id__ = commit_id =
|
|
24
|
+
__commit_id__ = commit_id = None
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
from collections.abc import Iterator, Mapping
|
|
2
|
+
from contextlib import contextmanager
|
|
3
|
+
from importlib import resources
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
from typing import Any
|
|
6
|
+
|
|
7
|
+
import copier
|
|
8
|
+
|
|
9
|
+
PathLike = str | Path
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
@contextmanager
|
|
13
|
+
def _bundled_scaffolding_path(scaffolding: str) -> Iterator[Path]:
|
|
14
|
+
scaffoldings = resources.files("modwire.modules") / "scaffoldings" / scaffolding
|
|
15
|
+
with resources.as_file(scaffoldings) as path:
|
|
16
|
+
yield path
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def generate_module(
|
|
20
|
+
module_name: str,
|
|
21
|
+
target_root: PathLike,
|
|
22
|
+
template_root: PathLike | None = None,
|
|
23
|
+
*,
|
|
24
|
+
scaffolding: str = "layered",
|
|
25
|
+
data: Mapping[str, Any] | None = None,
|
|
26
|
+
overwrite: bool = False,
|
|
27
|
+
skip_tasks: bool = True,
|
|
28
|
+
) -> None:
|
|
29
|
+
if template_root is not None:
|
|
30
|
+
_run_copy(
|
|
31
|
+
Path(template_root),
|
|
32
|
+
target_root,
|
|
33
|
+
module_name,
|
|
34
|
+
data=data,
|
|
35
|
+
overwrite=overwrite,
|
|
36
|
+
skip_tasks=skip_tasks,
|
|
37
|
+
)
|
|
38
|
+
return
|
|
39
|
+
|
|
40
|
+
with _bundled_scaffolding_path(scaffolding) as resolved_template_root:
|
|
41
|
+
_run_copy(
|
|
42
|
+
resolved_template_root,
|
|
43
|
+
target_root,
|
|
44
|
+
module_name,
|
|
45
|
+
data=data,
|
|
46
|
+
overwrite=overwrite,
|
|
47
|
+
skip_tasks=skip_tasks,
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def _run_copy(
|
|
52
|
+
template_root: Path,
|
|
53
|
+
target_root: PathLike,
|
|
54
|
+
module_name: str,
|
|
55
|
+
*,
|
|
56
|
+
data: Mapping[str, Any] | None,
|
|
57
|
+
overwrite: bool,
|
|
58
|
+
skip_tasks: bool,
|
|
59
|
+
) -> None:
|
|
60
|
+
resolved_template_root = template_root
|
|
61
|
+
if not resolved_template_root.exists():
|
|
62
|
+
raise FileNotFoundError(f"Copier template does not exist: {resolved_template_root}")
|
|
63
|
+
|
|
64
|
+
template_data = dict(data or {})
|
|
65
|
+
template_data["module_name"] = module_name
|
|
66
|
+
|
|
67
|
+
copier.run_copy(
|
|
68
|
+
str(resolved_template_root),
|
|
69
|
+
dst_path=target_root,
|
|
70
|
+
data=template_data,
|
|
71
|
+
defaults=True,
|
|
72
|
+
overwrite=overwrite,
|
|
73
|
+
skip_tasks=skip_tasks,
|
|
74
|
+
)
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
_min_copier_version: "9.0.0"
|
|
2
|
+
_templates_suffix: ".jinja"
|
|
3
|
+
_subdirectory: template
|
|
4
|
+
|
|
5
|
+
module_name:
|
|
6
|
+
type: str
|
|
7
|
+
default: sample_module
|
|
8
|
+
help: Importable module or package directory name.
|
|
9
|
+
|
|
10
|
+
aggregate_name:
|
|
11
|
+
type: str
|
|
12
|
+
default: SampleAggregate
|
|
13
|
+
help: Domain aggregate class name.
|
|
14
|
+
|
|
15
|
+
command_name:
|
|
16
|
+
type: str
|
|
17
|
+
default: CreateSample
|
|
18
|
+
help: Application command class name.
|
|
19
|
+
|
|
20
|
+
service_name:
|
|
21
|
+
type: str
|
|
22
|
+
default: SampleService
|
|
23
|
+
help: Application service class name.
|
|
24
|
+
|
|
25
|
+
repository_name:
|
|
26
|
+
type: str
|
|
27
|
+
default: SampleRepository
|
|
28
|
+
help: Repository port and adapter class suffix.
|
|
29
|
+
|
|
30
|
+
controller_name:
|
|
31
|
+
type: str
|
|
32
|
+
default: SampleController
|
|
33
|
+
help: UI/controller class name.
|
|
34
|
+
|
|
35
|
+
php_namespace:
|
|
36
|
+
type: str
|
|
37
|
+
default: App\\SampleModule
|
|
38
|
+
help: PHP namespace root for the generated classes.
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
<?php
|
|
2
|
+
|
|
3
|
+
declare(strict_types=1);
|
|
4
|
+
|
|
5
|
+
namespace {{ php_namespace }}\Application;
|
|
6
|
+
|
|
7
|
+
use {{ php_namespace }}\Domain\{{ aggregate_name }};
|
|
8
|
+
use {{ php_namespace }}\Ports\{{ repository_name }};
|
|
9
|
+
|
|
10
|
+
final class {{ service_name }}
|
|
11
|
+
{
|
|
12
|
+
public function __construct(
|
|
13
|
+
private readonly {{ repository_name }} $repository,
|
|
14
|
+
) {
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
public function handle({{ command_name }} $command): {{ aggregate_name }}
|
|
18
|
+
{
|
|
19
|
+
$aggregate = {{ aggregate_name }}::create($command->id, $command->name);
|
|
20
|
+
$this->repository->save($aggregate);
|
|
21
|
+
|
|
22
|
+
return $aggregate;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
<?php
|
|
2
|
+
|
|
3
|
+
declare(strict_types=1);
|
|
4
|
+
|
|
5
|
+
namespace {{ php_namespace }}\Domain;
|
|
6
|
+
|
|
7
|
+
final readonly class {{ aggregate_name }}
|
|
8
|
+
{
|
|
9
|
+
private function __construct(
|
|
10
|
+
public string $id,
|
|
11
|
+
public string $name,
|
|
12
|
+
) {
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
public static function create(string $id, string $name): self
|
|
16
|
+
{
|
|
17
|
+
if ($id === '') {
|
|
18
|
+
throw new \InvalidArgumentException('Aggregate id cannot be empty.');
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
if ($name === '') {
|
|
22
|
+
throw new \InvalidArgumentException('Aggregate name cannot be empty.');
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
return new self($id, $name);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
<?php
|
|
2
|
+
|
|
3
|
+
declare(strict_types=1);
|
|
4
|
+
|
|
5
|
+
namespace {{ php_namespace }}\Infrastructure;
|
|
6
|
+
|
|
7
|
+
use {{ php_namespace }}\Domain\{{ aggregate_name }};
|
|
8
|
+
use {{ php_namespace }}\Ports\{{ repository_name }};
|
|
9
|
+
|
|
10
|
+
final class InMemory{{ repository_name }} implements {{ repository_name }}
|
|
11
|
+
{
|
|
12
|
+
/** @var array<string, {{ aggregate_name }}> */
|
|
13
|
+
private array $items = [];
|
|
14
|
+
|
|
15
|
+
public function save({{ aggregate_name }} $aggregate): void
|
|
16
|
+
{
|
|
17
|
+
$this->items[$aggregate->id] = $aggregate;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
public function get(string $id): ?{{ aggregate_name }}
|
|
21
|
+
{
|
|
22
|
+
return $this->items[$id] ?? null;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
<?php
|
|
2
|
+
|
|
3
|
+
declare(strict_types=1);
|
|
4
|
+
|
|
5
|
+
namespace {{ php_namespace }}\Ports;
|
|
6
|
+
|
|
7
|
+
use {{ php_namespace }}\Domain\{{ aggregate_name }};
|
|
8
|
+
|
|
9
|
+
interface {{ repository_name }}
|
|
10
|
+
{
|
|
11
|
+
public function save({{ aggregate_name }} $aggregate): void;
|
|
12
|
+
|
|
13
|
+
public function get(string $id): ?{{ aggregate_name }};
|
|
14
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
<?php
|
|
2
|
+
|
|
3
|
+
declare(strict_types=1);
|
|
4
|
+
|
|
5
|
+
namespace {{ php_namespace }}\Ui;
|
|
6
|
+
|
|
7
|
+
use {{ php_namespace }}\Application\{{ command_name }};
|
|
8
|
+
use {{ php_namespace }}\Application\{{ service_name }};
|
|
9
|
+
|
|
10
|
+
final class {{ controller_name }}
|
|
11
|
+
{
|
|
12
|
+
public function __construct(
|
|
13
|
+
private readonly {{ service_name }} $service,
|
|
14
|
+
) {
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/** @return array{id: string, name: string} */
|
|
18
|
+
public function create(string $id, string $name): array
|
|
19
|
+
{
|
|
20
|
+
$aggregate = $this->service->handle(new {{ command_name }}($id, $name));
|
|
21
|
+
|
|
22
|
+
return [
|
|
23
|
+
'id' => $aggregate->id,
|
|
24
|
+
'name' => $aggregate->name,
|
|
25
|
+
];
|
|
26
|
+
}
|
|
27
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from . import domain, ports
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class {{ command_name }}:
|
|
7
|
+
def __init__(self, aggregate_id: str, name: str) -> None:
|
|
8
|
+
self.aggregate_id = aggregate_id
|
|
9
|
+
self.name = name
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class {{ service_name }}:
|
|
13
|
+
def __init__(self, repository: ports.{{ repository_name }}) -> None:
|
|
14
|
+
self._repository = repository
|
|
15
|
+
|
|
16
|
+
def handle(self, command: {{ command_name }}) -> domain.{{ aggregate_name }}:
|
|
17
|
+
aggregate = domain.{{ aggregate_name }}.create(command.aggregate_id, command.name)
|
|
18
|
+
self._repository.save(aggregate)
|
|
19
|
+
return aggregate
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
__all__ = ["{{ command_name }}", "{{ service_name }}"]
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
@dataclass(frozen=True)
|
|
7
|
+
class {{ aggregate_name }}:
|
|
8
|
+
aggregate_id: str
|
|
9
|
+
name: str
|
|
10
|
+
|
|
11
|
+
@classmethod
|
|
12
|
+
def create(cls, aggregate_id: str, name: str) -> "{{ aggregate_name }}":
|
|
13
|
+
if not aggregate_id:
|
|
14
|
+
raise ValueError("Aggregate id cannot be empty")
|
|
15
|
+
if not name:
|
|
16
|
+
raise ValueError("Aggregate name cannot be empty")
|
|
17
|
+
return cls(aggregate_id=aggregate_id, name=name)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
__all__ = ["{{ aggregate_name }}"]
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from . import domain
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class InMemory{{ repository_name }}:
|
|
7
|
+
def __init__(self) -> None:
|
|
8
|
+
self._items: dict[str, domain.{{ aggregate_name }}] = {}
|
|
9
|
+
|
|
10
|
+
def save(self, aggregate: domain.{{ aggregate_name }}) -> None:
|
|
11
|
+
self._items[aggregate.aggregate_id] = aggregate
|
|
12
|
+
|
|
13
|
+
def get(self, aggregate_id: str) -> domain.{{ aggregate_name }} | None:
|
|
14
|
+
return self._items.get(aggregate_id)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
__all__ = ["InMemory{{ repository_name }}"]
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import Protocol
|
|
4
|
+
|
|
5
|
+
from . import domain
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class {{ repository_name }}(Protocol):
|
|
9
|
+
def save(self, aggregate: domain.{{ aggregate_name }}) -> None:
|
|
10
|
+
raise NotImplementedError
|
|
11
|
+
|
|
12
|
+
def get(self, aggregate_id: str) -> domain.{{ aggregate_name }} | None:
|
|
13
|
+
raise NotImplementedError
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
__all__ = ["{{ repository_name }}"]
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from . import application, infrastructure
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class {{ controller_name }}:
|
|
7
|
+
def __init__(self, service: application.{{ service_name }}) -> None:
|
|
8
|
+
self._service = service
|
|
9
|
+
|
|
10
|
+
def create(self, aggregate_id: str, name: str) -> dict[str, str]:
|
|
11
|
+
aggregate = self._service.handle(application.{{ command_name }}(aggregate_id, name))
|
|
12
|
+
return {"id": aggregate.aggregate_id, "name": aggregate.name}
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def bootstrap() -> {{ controller_name }}:
|
|
16
|
+
repository = infrastructure.InMemory{{ repository_name }}()
|
|
17
|
+
service = application.{{ service_name }}(repository)
|
|
18
|
+
return {{ controller_name }}(service)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
__all__ = ["{{ controller_name }}", "bootstrap"]
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { {{ aggregate_name }} } from "./domain";
|
|
2
|
+
import { {{ repository_name }} } from "./ports";
|
|
3
|
+
|
|
4
|
+
export class {{ command_name }} {
|
|
5
|
+
constructor(
|
|
6
|
+
public readonly aggregateId: string,
|
|
7
|
+
public readonly name: string,
|
|
8
|
+
) {}
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export class {{ service_name }} {
|
|
12
|
+
constructor(private readonly repository: {{ repository_name }}) {}
|
|
13
|
+
|
|
14
|
+
handle(command: {{ command_name }}): {{ aggregate_name }} {
|
|
15
|
+
const aggregate = {{ aggregate_name }}.create(command.aggregateId, command.name);
|
|
16
|
+
this.repository.save(aggregate);
|
|
17
|
+
|
|
18
|
+
return aggregate;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export class {{ aggregate_name }} {
|
|
2
|
+
private constructor(
|
|
3
|
+
public readonly aggregateId: string,
|
|
4
|
+
public readonly name: string,
|
|
5
|
+
) {}
|
|
6
|
+
|
|
7
|
+
static create(aggregateId: string, name: string): {{ aggregate_name }} {
|
|
8
|
+
if (aggregateId.length === 0) {
|
|
9
|
+
throw new Error("Aggregate id cannot be empty.");
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
if (name.length === 0) {
|
|
13
|
+
throw new Error("Aggregate name cannot be empty.");
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
return new {{ aggregate_name }}(aggregateId, name);
|
|
17
|
+
}
|
|
18
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { {{ aggregate_name }} } from "./domain";
|
|
2
|
+
import { {{ repository_name }} } from "./ports";
|
|
3
|
+
|
|
4
|
+
export class InMemory{{ repository_name }} implements {{ repository_name }} {
|
|
5
|
+
private readonly items = new Map<string, {{ aggregate_name }}>();
|
|
6
|
+
|
|
7
|
+
save(aggregate: {{ aggregate_name }}): void {
|
|
8
|
+
this.items.set(aggregate.aggregateId, aggregate);
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
get(aggregateId: string): {{ aggregate_name }} | undefined {
|
|
12
|
+
return this.items.get(aggregateId);
|
|
13
|
+
}
|
|
14
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { {{ command_name }}, {{ service_name }} } from "./application";
|
|
2
|
+
import { InMemory{{ repository_name }} } from "./infrastructure";
|
|
3
|
+
|
|
4
|
+
export class {{ controller_name }} {
|
|
5
|
+
constructor(private readonly service: {{ service_name }}) {}
|
|
6
|
+
|
|
7
|
+
create(aggregateId: string, name: string): { id: string; name: string } {
|
|
8
|
+
const aggregate = this.service.handle(new {{ command_name }}(aggregateId, name));
|
|
9
|
+
|
|
10
|
+
return { id: aggregate.aggregateId, name: aggregate.name };
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function bootstrap(): {{ controller_name }} {
|
|
15
|
+
const repository = new InMemory{{ repository_name }}();
|
|
16
|
+
const service = new {{ service_name }}(repository);
|
|
17
|
+
|
|
18
|
+
return new {{ controller_name }}(service);
|
|
19
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
_min_copier_version: "9.0.0"
|
|
2
|
+
_templates_suffix: ".jinja"
|
|
3
|
+
_subdirectory: template
|
|
4
|
+
|
|
5
|
+
module_name:
|
|
6
|
+
type: str
|
|
7
|
+
default: sample_package
|
|
8
|
+
help: Importable package name that will own the four layer files.
|
|
9
|
+
|
|
10
|
+
model_name:
|
|
11
|
+
type: str
|
|
12
|
+
default: SampleItem
|
|
13
|
+
help: Pydantic domain model class name.
|
|
14
|
+
|
|
15
|
+
repository_name:
|
|
16
|
+
type: str
|
|
17
|
+
default: SampleRepository
|
|
18
|
+
help: Infrastructure repository class name.
|
|
19
|
+
|
|
20
|
+
service_name:
|
|
21
|
+
type: str
|
|
22
|
+
default: SampleApplication
|
|
23
|
+
help: Application service class name.
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from . import domain, infrastructure
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class {{ service_name }}:
|
|
7
|
+
def __init__(self, repository: infrastructure.{{ repository_name }}) -> None:
|
|
8
|
+
self._repository = repository
|
|
9
|
+
|
|
10
|
+
def handle(self, name: str) -> domain.{{ model_name }}:
|
|
11
|
+
return self._repository.find(name)
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def bootstrap() -> {{ service_name }}:
|
|
15
|
+
return {{ service_name }}(infrastructure.{{ repository_name }}())
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
__all__ = ["{{ service_name }}", "bootstrap"]
|