Skip to content

Commit

Permalink
Feat/api architecture review (#124)
Browse files Browse the repository at this point in the history
* feat(http): refactor http layer

* feat(api): implement api layer for pizza-store service

* feat(users): implement users page

* docs: refactor architecture docs

* refactor: fix sonar issues
  • Loading branch information
rodmax authored Dec 5, 2020
1 parent 89d1a2c commit 4bafc80
Show file tree
Hide file tree
Showing 72 changed files with 1,889 additions and 393 deletions.
1 change: 1 addition & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
"depcruise",
"devtool",
"devtools",
"mockapi",
"mqpacker"
],
"[javascript]": {
Expand Down
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ and i am now implementing this ideas
- [x] Follow [redux architecture StyleGuide](https://redux.js.org/style-guide/style-guide) rules whenever it is reasonably possible
- [ ] localization
- [ ] form handling(ui/server validation, form state)
- [ ] folder structure: description and rules
- [x] folder structure: description and rules
- Store
- [x] Redux (fully type safe at first and with reduced boilerplate at second)
- [ ] try [immer](https://immerjs.github.io/immer/docs/introduction) (looks nice and recommended by redux team)
Expand All @@ -79,7 +79,7 @@ and i am now implementing this ideas
- [ ] auth logic (route guards/permissions for individual features)
- API client
- [x] Proof of concent using RxJs builtin clients(fetch/xhr)
- [ ] API client factory(rxjs based)
- [x] API client factory(rxjs based)
- [ ] File uploading
- [ ] Catch unhandled API errors on App level
- React related:
Expand Down
20 changes: 20 additions & 0 deletions depcruise/depcruise-graph.api.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
// @ts-check
const common = require('./depcruise.common')

module.exports = common
.optionsBuilder()
.withReporterOptions({
dot: {
theme: {
graph: { rankdir: 'TD' },
},
filters: {
includeOnly: {},
focus: {},
exclude: {
path: ['^node_modules', ...common.ANY_TESTS, ...common.ANY_TYPINGS],
},
},
},
})
.options()
26 changes: 26 additions & 0 deletions depcruise/depcruise-graph.src.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
// @ts-check
const common = require('./depcruise.common')

module.exports = common
.optionsBuilder()
.withReporterOptions({
dot: {
theme: {
graph: { rankdir: 'TD' },
},
collapsePattern: [
'src/common/[^/]+',
'src/api/[^/]+',
'src/modules/[^/]+',
'src/app/[^/]+',
],
filters: {
includeOnly: {},
focus: {},
exclude: {
path: ['^node_modules', ...common.ANY_TESTS, ...common.ANY_TYPINGS],
},
},
},
})
.options()
75 changes: 75 additions & 0 deletions depcruise/depcruise.common.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
// @ts-check

const common = {
COMMON_ANY: '^src/common/',
API_ANY: '^src/api/',
MODULES_ANY: '^src/modules/',
MODULE_PAGES: '^src/modules/\\w+/pages/',
MODULE_MODEL: '^src/modules/\\w+/model/',
MODULE_UI: '^src/modules/\\w+/ui/',
APP_ANY: '^src/app/',
ANY_TESTS: ['testing/', '\\.(spec|testing|factories)\\.(tsx|ts)$'],
ANY_TYPINGS: ['(types|typings)/', '(type|types|typings)\\.(tsx|ts)$'],
optionsBuilder,
}

function optionsBuilder() {
/**
* @type { import('./depcruise.types').DepcruiseConfig['forbidden'] }
*/
let forbidden = []

/**
* @type { import('./depcruise.types').DepcruiseConfig['options']['reporterOptions'] }
*/
let reporterOptions = {}

const builder = {
/**
* @param {typeof forbidden } rules
* @return {typeof builder}
*/
withForbidden(rules) {
forbidden = rules
return builder
},
/**
* @param {typeof reporterOptions } options
* @return {typeof builder}
*/
withReporterOptions(options) {
reporterOptions = options
return builder
},
/**
* @param {typeof reporterOptions } options
* @return { import('./depcruise.types').DepcruiseConfig }
*/
options() {
return {
forbidden,
options: {
doNotFollow: {
path: 'node_modules',
dependencyTypes: [
'npm',
'npm-dev',
'npm-optional',
'npm-peer',
'npm-bundled',
'npm-no-pkg',
],
},
tsPreCompilationDeps: true,
tsConfig: {
fileName: 'tsconfig.json',
},
reporterOptions,
},
}
},
}
return builder
}

module.exports = common
4 changes: 4 additions & 0 deletions depcruise/depcruise.types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import { IOptions, IRuleSetType } from 'dependency-cruiser'
import { OmitStrict } from '../src/common/typings/omit-strict.typings'

export type DepcruiseConfig = { options: IOptions } & OmitStrict<IRuleSetType, 'options'>
97 changes: 19 additions & 78 deletions .dependency-cruiser.js → depcruise/depcruise.validate.js
Original file line number Diff line number Diff line change
@@ -1,29 +1,9 @@
// @ts-check
const testsRelatedPaths = ['\\.spec\\.(tsx|ts)$', 'testing/', 'factories\\.ts']
const common = require('./depcruise.common')

const COMMON_ANY = '^src/common/'
const API_ANY = '^src/api/'

const MODULES_ANY = '^src/modules/'
const MODULE_PAGES = '^src/modules/\\w+/pages/'
const MODULE_MODEL = '^src/modules/\\w+/model/'
const MODULE_UI = '^src/modules/\\w+/ui/'

const APP_ANY = '^src/app/'

/**
* @typedef { { options: import('dependency-cruiser').IOptions} } DepcruiseOptions
*/

/**
* @typedef { Omit<import('dependency-cruiser').IRuleSetType, 'options'> & DepcruiseOptions } DepcruiseConfig
*/

/**
* @type { DepcruiseConfig }
*/
const config = {
forbidden: [
module.exports = common
.optionsBuilder()
.withForbidden([
/* rules from the 'recommended' preset: */
{
name: 'no-circular',
Expand Down Expand Up @@ -128,7 +108,7 @@ const config = {
'from.pathNot re of the not-to-dev-dep rule in the dependency-cruiser configuration',
from: {
path: '^(src)',
pathNot: testsRelatedPaths,
pathNot: common.ANY_TESTS,
},
to: {
dependencyTypes: ['npm-dev'],
Expand Down Expand Up @@ -164,50 +144,50 @@ const config = {
name: 'not-from-any-to-app',
severity: 'error',
from: {
path: [MODULES_ANY, API_ANY, COMMON_ANY],
path: [common.MODULES_ANY, common.API_ANY, common.COMMON_ANY],
},
to: {
path: APP_ANY,
path: common.APP_ANY,
},
},
{
name: 'not-from-api-or-common-to-modules',
severity: 'error',
from: {
path: [API_ANY, COMMON_ANY],
path: [common.API_ANY, common.COMMON_ANY],
},
to: {
path: MODULES_ANY,
path: common.MODULES_ANY,
},
},
{
name: 'not-from-common-to-api',
severity: 'error',
from: {
path: COMMON_ANY,
path: common.COMMON_ANY,
},
to: {
path: API_ANY,
path: common.API_ANY,
},
},
{
name: 'module:not-from-any-to-pages',
severity: 'error',
from: {
path: [MODULE_UI, MODULE_MODEL],
path: [common.MODULE_UI, common.MODULE_MODEL],
},
to: {
path: MODULE_PAGES,
path: common.MODULE_PAGES,
},
},
{
name: 'module:not-from-model-to-ui',
severity: 'error',
from: {
path: MODULE_MODEL,
path: common.MODULE_MODEL,
},
to: {
path: MODULE_UI,
path: common.MODULE_UI,
},
},
{
Expand All @@ -221,50 +201,11 @@ const config = {
'from.pathNot re of the not-to-dev-dep rule in the dependency-cruiser configuration',
from: {
path: '^(src)',
pathNot: testsRelatedPaths,
pathNot: common.ANY_TESTS,
},
to: {
path: testsRelatedPaths,
path: common.ANY_TESTS,
},
},
],
options: {
doNotFollow: {
path: 'node_modules',
dependencyTypes: [
'npm',
'npm-dev',
'npm-optional',
'npm-peer',
'npm-bundled',
'npm-no-pkg',
],
},
tsPreCompilationDeps: true,
tsConfig: {
fileName: 'tsconfig.json',
},
reporterOptions: {
dot: {
theme: {
graph: { rankdir: 'TD' },
},
collapsePattern: [
'src/common/[^/]+',
'src/api/[^/]+',
'src/modules/[^/]+',
'src/app/[^/]+',
],
filters: {
includeOnly: {},
focus: {},
exclude: {
path: ['^node_modules'],
},
},
},
},
},
}

module.exports = config
])
.options()
25 changes: 11 additions & 14 deletions docs/application-structure.md → docs/architecture.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Application structure
# Architecture

Here we give a set of principles and rules that the application (folder) structure follows
Here we give a set of architectural principles and rules that the application follows

## LIFT

Expand Down Expand Up @@ -42,7 +42,7 @@ user/

Below is a diagram showing the dependency rules in the application<br>
[dependency-cruiser](https://github.com/sverweij/dependency-cruiser) utility used to control this dependencies<br>
(See [.dependency-cruiser.js](../.dependency-cruiser.js) for details)
(See [depcruise/](../depcruise) configs for details)

```mermaid
flowchart
Expand Down Expand Up @@ -70,17 +70,14 @@ flowchart
```

<details>
<summary>Expand me to see this repository dependency graph 👇</summary>
<img src="./graph.svg">
</details>

## Guidelines
and below is how it looks in real code

### Feature module
<img src="./graph.svg">

Feature module code lives in `modules/{feature}`. This folder contains all feature related
## Layers

- pages: `modules/{feature}/pages/**`
- ui components(used by own pages or another feature components): `modules/{feature}/ui/**`
- code which not related to certain component/page and may be reused between different ui components(business logic, configs, view models, etc): `modules/{feature}/model/**`
- [app layer](layer-0-app.md)
- [modules layer](layer-1-modules.md)
- [api layer](layer-2-api.md)
- [common layer](layer-3-common.md)
- third-party libs layer
Loading

0 comments on commit 4bafc80

Please sign in to comment.