Skip to content

Commit

Permalink
clean architecture
Browse files Browse the repository at this point in the history
  • Loading branch information
teintinu committed Jul 26, 2021
1 parent 1705fbb commit 6b1153f
Show file tree
Hide file tree
Showing 16 changed files with 345 additions and 261 deletions.
6 changes: 5 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -14,4 +14,8 @@ snapshot/**/yarn-lock
dist/

examples/new/a
examples/new/b
examples/new/b

examples/clean-architecture/business-rules
examples/clean-architecture/apps
examples/clean-architecture/adapters
9 changes: 9 additions & 0 deletions .vscode/launch.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,15 @@
"program": "${workspaceFolder}/main.go",
"cwd": "${workspaceFolder}/examples/simple-workspace",
"args": ["deps"]
},
{
"name": "clean-architecture(deps)",
"type": "go",
"request": "launch",
"mode": "auto",
"program": "${workspaceFolder}/main.go",
"cwd": "${workspaceFolder}/examples/clean-architecture",
"args": ["deps"]
}
]
}
45 changes: 33 additions & 12 deletions docs/monoclean-schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,30 @@
"$id": "http://example.com/example.json",
"type": "object",
"required": [
"packages",
"workspace"
],
"anyOf": [
{
"required": [
"packages"
]
},
{
"required": [
"business-rules"
]
},
{
"required": [
"executables"
]
},
{
"required": [
"adapters"
]
}
],
"properties": {
"engines": {
"$id": "#/properties/engines",
Expand Down Expand Up @@ -64,25 +85,25 @@
},
"additionalProperties": false
},
"packages": {
"$id": "#/properties/packages",
"$ref": "#/definitions/packageList",
"title": "The packages on this workspace"
},
"business-rules": {
"$id": "#/properties/packages",
"$ref": "#/definitions/packageList",
"$ref": "#/definitions/packageList",
"title": "These Clean Architecture packages defines business-rules"
},
"applications": {
"executables": {
"$id": "#/properties/packages",
"$ref": "#/definitions/packageList",
"title": "These Clean Architecture packages defines applications"
"$ref": "#/definitions/packageList",
"title": "These Clean Architecture packages defines executables"
},
"adapters": {
"$id": "#/properties/packages",
"$ref": "#/definitions/packageList",
"$ref": "#/definitions/packageList",
"title": "These Clean Architecture packages defines adapters"
},
"packages": {
"$id": "#/properties/packages",
"$ref": "#/definitions/packageList",
"title": "The packages on this workspace"
}
},
"definitions": {
Expand Down Expand Up @@ -130,7 +151,7 @@
"dependencies": {
"$ref": "#/definitions/dependencies",
"title": "The dependencies for all workspace"
}
}
},
"additionalProperties": false
}
Expand Down
15 changes: 15 additions & 0 deletions examples/clean-architecture/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# monoclean
node_modules
.nvm.rc
package.json
package-lock.json
yarn-lock.*
yarn.lock
tsconfig.**
jest.config.js
tmp
out
.eslintrc.json
coverage
.vscode

21 changes: 21 additions & 0 deletions examples/clean-architecture/monoclean.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
workspace:
name: "todo"
version: "1.0.0"

business-rules:
"@todo/business-rules":
folder: "business-rules"

adapters:
"@todo/fake-repository":
folder: "adapters/fake-repository"

"@todo/postgres-repository":
folder: "adapters/postgres-repository"

executables:
"@todo/server":
folder: "apps/server"

"@todo/client":
folder: "apps/client"
13 changes: 9 additions & 4 deletions internal/build.go
Original file line number Diff line number Diff line change
Expand Up @@ -87,12 +87,17 @@ func Build(repo *Repository, opts BuildOptions) error {
}

bin := make(map[string]string)
for executableName, executable := range pkg.Executables {
buildOpts.EntryPoints = append(buildOpts.EntryPoints, executable.Entrypoint)

{

executableName := "index.js"
executableEntrypoint := "index.ts"

buildOpts.EntryPoints = append(buildOpts.EntryPoints, executableEntrypoint)
bin[executableName] = executableName

entrypointExt := path.Ext(executable.Entrypoint)
entrypointOut := strings.TrimSuffix(path.Base(executable.Entrypoint), entrypointExt) + ".js"
entrypointExt := path.Ext(executableEntrypoint)
entrypointOut := strings.TrimSuffix(path.Base(executableEntrypoint), entrypointExt) + ".js"

// See also `script` in Run.
shim := fmt.Sprintf(`#!/usr/bin/env node
Expand Down
15 changes: 9 additions & 6 deletions internal/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,15 @@ package internal
// preserve order if necessary.

type Config struct {
Engines map[string]string
Repository string
Registry string
Workspace WorkspaceConfig
Packages map[string]PackageConfig
Dependencies map[string]string
Engines map[string]string
Repository string
Registry string
Workspace WorkspaceConfig
Dependencies map[string]string
Packages map[string]PackageConfig
BusinessRules map[string]PackageConfig `yaml:"business-rules"`
Executables map[string]PackageConfig
Adapters map[string]PackageConfig
}

type WorkspaceConfig struct {
Expand Down
133 changes: 95 additions & 38 deletions internal/repository.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,11 @@ type Repository struct {
Engines map[string]string
IsWorkspace bool
Workspace Workspace
Packages map[string]*Package
Dependencies map[string]*Dependency
Url string
Registry string

Packages map[string]*Package
}

type Workspace struct {
Expand All @@ -36,6 +37,15 @@ type Dependency struct {
Version string
}

type PackageLayer = int

const (
NormalLayer PackageLayer = iota
BusinessRulesLayer
ExecutablesLayer
AdaptersLayer
)

type Package struct {
Name string
Version string
Expand All @@ -46,11 +56,9 @@ type Package struct {
usesWebWorker bool
Description string
Index string
Layer PackageLayer
Dependencies map[string]*Dependency
Executables map[string]*Executable
}

type TsConfig struct {
IsExecutable bool
}

type Executable struct {
Expand Down Expand Up @@ -107,40 +115,82 @@ func LoadRepository(searchDir string) (*Repository, error) {
}

repo.Packages = make(map[string]*Package)
for packageName, packageConfig := range cfg.Packages {
err = loadPackages(repo, cfg.Packages, NormalLayer)
if err != nil {
return nil, err
}
err = loadPackages(repo, cfg.BusinessRules, BusinessRulesLayer)
if err != nil {
return nil, err
}
err = loadPackages(repo, cfg.Executables, ExecutablesLayer)
if err != nil {
return nil, err
}
err = loadPackages(repo, cfg.Adapters, AdaptersLayer)
if err != nil {
return nil, err
}

repo.Dependencies = make(map[string]*Dependency)
addDependency := func(name, version string) {
repo.Dependencies[name] = &Dependency{
Name: name,
Version: version,
}
}
for dependencyName, dependencyVersion := range cfg.Dependencies {
addDependency(dependencyName, dependencyVersion)
}
var requiredDeps map[string]string
if repo.IsWorkspace {
requiredDeps = requiredDependenciesWorkspace
} else {
requiredDeps = requiredDependenciesMix
}
for dependencyName, dependencyVersion := range requiredDeps {
if _, ok := repo.Dependencies[dependencyName]; !ok {
addDependency(dependencyName, dependencyVersion)
}
}
for pkgName := range repo.Packages {
err = validateLayer(repo, repo.Packages[pkgName])
if err != nil {
return nil, err
}
}
return &repo, nil
}

func loadPackages(repo Repository, packages map[string]PackageConfig, layer PackageLayer) error {
for packageName, packageConfig := range packages {
if repo.IsWorkspace {
if len(packageConfig.Index) > 0 {
return nil, errors.New("use folder instead index inside workspace")
return errors.New("use folder instead index inside workspace")
}
if packageConfig.Folder == "" {
return nil, errors.New("use folder for each package in workspace")
return errors.New("use folder for each package in workspace")
}
if strings.Contains(packageConfig.Folder, "\\") {
return nil, errors.New(packageConfig.Folder + " user normal slashes")
return errors.New(packageConfig.Folder + " user normal slashes")
}
if strings.HasPrefix(packageConfig.Folder, ".") || strings.HasPrefix(packageConfig.Folder, "/") {
return nil, errors.New(packageConfig.Folder + " must be relative to workspace folder")
return errors.New(packageConfig.Folder + " must be relative to workspace folder")
}
if strings.HasSuffix(packageConfig.Folder, ".") || strings.HasSuffix(packageConfig.Folder, "/") {
return nil, errors.New(packageConfig.Folder + " folder ends with a invalid char")
return errors.New(packageConfig.Folder + " folder ends with a invalid char")
}
}
if packageConfig.Dependencies != nil && (!repo.IsWorkspace) {
return nil, errors.New("package dependencies is supported only inside workspace")
return errors.New("package dependencies is supported only inside workspace")
}
pkg := &Package{
Name: packageName,
Public: packageConfig.Public,
Description: packageConfig.Description,
Index: packageConfig.Index,
Folder: packageConfig.Folder,
}
pkg.Executables = make(map[string]*Executable)
for executableName, executableEntrypoint := range packageConfig.Executables {
pkg.Executables[executableName] = &Executable{
Name: executableName,
Entrypoint: executableEntrypoint,
}
Layer: layer,
}
if repo.IsWorkspace && packageConfig.Dependencies != nil {
pkg.Dependencies = make(map[string]*Dependency)
Expand All @@ -155,30 +205,37 @@ func LoadRepository(searchDir string) (*Repository, error) {
}
repo.Packages[packageName] = pkg
}
return nil
}

repo.Dependencies = make(map[string]*Dependency)
addDependency := func(name, version string) {
repo.Dependencies[name] = &Dependency{
Name: name,
Version: version,
}
func validateLayer(repo Repository, pkg *Package) error {
if pkg.Layer == NormalLayer {
return nil
}
for dependencyName, dependencyVersion := range cfg.Dependencies {
addDependency(dependencyName, dependencyVersion)
if pkg.Layer == BusinessRulesLayer {
if pkg.IsExecutable {
return errors.New("Business layer " + pkg.Name + " can't be an executble")
}
for depName := range pkg.Dependencies {
var depPkg = repo.Packages[depName]
if depPkg == nil {
return errors.New("don't use external dependency " + depName + " on business layer " + pkg.Name)
}
if depPkg.Layer == AdaptersLayer {
return errors.New("business layer " + pkg.Name + "can't depends of adapter layer " + depName)
}
if depPkg.Layer == ExecutablesLayer {
return errors.New("business layer " + pkg.Name + "can't depends of executable layer " + depName)
}
}
}
var requiredDeps map[string]string
if repo.IsWorkspace {
requiredDeps = requiredDependenciesWorkspace
} else {
requiredDeps = requiredDependenciesMix
if pkg.Layer == AdaptersLayer && pkg.IsExecutable {
return errors.New("Adapter layer " + pkg.Name + " can't be an executble")
}
for dependencyName, dependencyVersion := range requiredDeps {
if _, ok := repo.Dependencies[dependencyName]; !ok {
addDependency(dependencyName, dependencyVersion)
}
if pkg.Layer == ExecutablesLayer && pkg.IsExecutable {
return errors.New("Adapter layer " + pkg.Name + " can't be an executble")
}

return &repo, nil
return nil
}

const configName = "monoclean.yml"
Expand Down
Loading

0 comments on commit 6b1153f

Please sign in to comment.