├── .gitignore
├── .travis.yml
├── README.md
├── build.gradle
├── build.sh
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
├── settings.gradle
└── src
├── main
├── kotlin
│ └── pl
│ │ └── braintelligence
│ │ └── projectmanager
│ │ ├── Application.kt
│ │ ├── core
│ │ ├── projects
│ │ │ ├── domain
│ │ │ │ ├── Feature.kt
│ │ │ │ ├── PriorityLevel.kt
│ │ │ │ ├── Project.kt
│ │ │ │ ├── ProjectCreatorService.kt
│ │ │ │ ├── ProjectExceptions.kt
│ │ │ │ ├── ProjectFactory.kt
│ │ │ │ ├── ProjectQueryService.kt
│ │ │ │ ├── Status.kt
│ │ │ │ └── configuration
│ │ │ │ │ ├── InMemoryProjectRepository.kt
│ │ │ │ │ └── TeamConfiguration.kt
│ │ │ └── ports
│ │ │ │ ├── incoming
│ │ │ │ ├── ProjectCreatorPort.kt
│ │ │ │ └── ProjectQueryPort.kt
│ │ │ │ └── outgoing
│ │ │ │ ├── ProjectCreatorRepository.kt
│ │ │ │ └── ProjectQueryRepository.kt
│ │ └── team
│ │ │ ├── domain
│ │ │ ├── Employee.kt
│ │ │ ├── JobPosition.kt
│ │ │ ├── Team.kt
│ │ │ ├── TeamExceptions.kt
│ │ │ ├── TeamFacade.kt
│ │ │ └── configuration
│ │ │ │ ├── InMemoryTeamRepository.kt
│ │ │ │ └── TeamConfiguration.kt
│ │ │ └── ports
│ │ │ ├── incoming
│ │ │ └── TeamManager.kt
│ │ │ └── outgoing
│ │ │ └── TeamRepository.kt
│ │ ├── infrastructure
│ │ ├── adapter
│ │ │ ├── incoming
│ │ │ │ └── rest
│ │ │ │ │ ├── ProjectController.kt
│ │ │ │ │ ├── TeamController.kt
│ │ │ │ │ └── dto
│ │ │ │ │ ├── ProjectDtos.kt
│ │ │ │ │ └── TeamDtos.kt
│ │ │ └── outgoing
│ │ │ │ └── mongo
│ │ │ │ ├── project
│ │ │ │ ├── TeamCreatorRepository.kt
│ │ │ │ ├── TeamQueryRepository.kt
│ │ │ │ └── entities
│ │ │ │ │ ├── DbFeature.kt
│ │ │ │ │ └── DbProject.kt
│ │ │ │ └── team
│ │ │ │ ├── MongoTeamRepository.kt
│ │ │ │ └── entities
│ │ │ │ ├── DbEmployee.kt
│ │ │ │ └── DbTeam.kt
│ │ └── error
│ │ │ └── ErrorHandler.kt
│ │ └── shared
│ │ ├── DomainException.kt
│ │ └── InMemoryRepository.kt
└── resources
│ └── application.yml
└── test
├── groovy
└── pl
│ └── braintelligence
│ └── projectmanager
│ ├── base
│ ├── BaseDtoObjects.groovy
│ ├── BaseIntegrationTest.groovy
│ ├── BaseUnitTest.groovy
│ └── http
│ │ └── BaseHttpMethods.groovy
│ ├── project
│ ├── ProjectAcceptanceTest.groovy
│ ├── base
│ │ └── BaseProjectUnitTest.groovy
│ └── domain
│ │ ├── ProjectCreationTest.groovy
│ │ └── ProjectQueryTest.groovy
│ └── team
│ ├── TeamAcceptanceTest.groovy
│ ├── base
│ └── BaseTeamUnitTest.groovy
│ └── domain
│ ├── AddingTeamMembersToTeamTest.groovy
│ ├── TeamCreationTest.groovy
│ ├── TeamMembersValidationTest.groovy
│ └── TeamValidationTest.groovy
└── kotlin
└── pl
└── braintelligence
└── projectmanager
└── core
└── team
├── HexagonalArchitectureTest.kt
└── NoSpringInDomainTest.kt
/.gitignore:
--------------------------------------------------------------------------------
1 |
2 | # Created by https://www.gitignore.io/api/web,git,node,java,maven,macos,linux,gradle,kotlin,eclipse,vagrant,netbeans,intellij,archlinuxpackages
3 | # Edit at https://www.gitignore.io/?templates=web,git,node,java,maven,macos,linux,gradle,kotlin,eclipse,vagrant,netbeans,intellij,archlinuxpackages
4 |
5 | ### ArchLinuxPackages ###
6 | *.tar
7 | *.tar.*
8 | *.jar
9 | *.exe
10 | *.msi
11 | *.zip
12 | *.tgz
13 | *.log
14 | *.log.*
15 | *.sig
16 |
17 | pkg/
18 | src/
19 |
20 | ### Eclipse ###
21 | .metadata
22 | bin/
23 | tmp/
24 | *.tmp
25 | *.bak
26 | *.swp
27 | *~.nib
28 | local.properties
29 | .settings/
30 | .loadpath
31 | .recommenders
32 |
33 | # External tool builders
34 | .externalToolBuilders/
35 |
36 | # Locally stored "Eclipse launch configurations"
37 | *.launch
38 |
39 | # PyDev specific (Python IDE for Eclipse)
40 | *.pydevproject
41 |
42 | # CDT-specific (C/C++ Development Tooling)
43 | .cproject
44 |
45 | # CDT- autotools
46 | .autotools
47 |
48 | # Java annotation processor (APT)
49 | .factorypath
50 |
51 | # PDT-specific (PHP Development Tools)
52 | .buildpath
53 |
54 | # sbteclipse plugin
55 | .target
56 |
57 | # Tern plugin
58 | .tern-project
59 |
60 | # TeXlipse plugin
61 | .texlipse
62 |
63 | # STS (Spring Tool Suite)
64 | .springBeans
65 |
66 | # Code Recommenders
67 | .recommenders/
68 |
69 | # Annotation Processing
70 | .apt_generated/
71 |
72 | # Scala IDE specific (Scala & Java development for Eclipse)
73 | .cache-main
74 | .scala_dependencies
75 | .worksheet
76 |
77 | ### Eclipse Patch ###
78 | # Eclipse Core
79 | .project
80 |
81 | # JDT-specific (Eclipse Java Development Tools)
82 | .classpath
83 |
84 | # Annotation Processing
85 | .apt_generated
86 |
87 | .sts4-cache/
88 |
89 | ### Git ###
90 | # Created by git for backups. To disable backups in Git:
91 | # $ git config --global mergetool.keepBackup false
92 | *.orig
93 |
94 | # Created by git when using merge tools for conflicts
95 | *.BACKUP.*
96 | *.BASE.*
97 | *.LOCAL.*
98 | *.REMOTE.*
99 | *_BACKUP_*.txt
100 | *_BASE_*.txt
101 | *_LOCAL_*.txt
102 | *_REMOTE_*.txt
103 |
104 | ### Intellij ###
105 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and WebStorm
106 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
107 |
108 | # User-specific stuff
109 | .idea/**/workspace.xml
110 | .idea/**/tasks.xml
111 | .idea/**/usage.statistics.xml
112 | .idea/**/dictionaries
113 | .idea/**/shelf
114 |
115 | # Generated files
116 | .idea/**/contentModel.xml
117 |
118 | # Sensitive or high-churn files
119 | .idea/**/dataSources/
120 | .idea/**/dataSources.ids
121 | .idea/**/dataSources.local.xml
122 | .idea/**/sqlDataSources.xml
123 | .idea/**/dynamic.xml
124 | .idea/**/uiDesigner.xml
125 | .idea/**/dbnavigator.xml
126 |
127 | # Gradle
128 | .idea/**/gradle.xml
129 | .idea/**/libraries
130 |
131 | # Gradle and Maven with auto-import
132 | # When using Gradle or Maven with auto-import, you should exclude module files,
133 | # since they will be recreated, and may cause churn. Uncomment if using
134 | # auto-import.
135 | # .idea/modules.xml
136 | # .idea/*.iml
137 | # .idea/modules
138 |
139 | # CMake
140 | cmake-build-*/
141 |
142 | # Mongo Explorer plugin
143 | .idea/**/mongoSettings.xml
144 |
145 | # File-based project format
146 | *.iws
147 |
148 | # IntelliJ
149 | out/
150 |
151 | # mpeltonen/sbt-idea plugin
152 | .idea_modules/
153 |
154 | # JIRA plugin
155 | atlassian-ide-plugin.xml
156 |
157 | # Cursive Clojure plugin
158 | .idea/replstate.xml
159 |
160 | # Crashlytics plugin (for Android Studio and IntelliJ)
161 | com_crashlytics_export_strings.xml
162 | crashlytics.properties
163 | crashlytics-build.properties
164 | fabric.properties
165 |
166 | # Editor-based Rest Client
167 | .idea/httpRequests
168 |
169 | # Android studio 3.1+ serialized cache file
170 | .idea/caches/build_file_checksums.ser
171 |
172 | ### Intellij Patch ###
173 | # Comment Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-215987721
174 |
175 | # *.iml
176 | # modules.xml
177 | # .idea/misc.xml
178 | # *.ipr
179 |
180 | # Sonarlint plugin
181 | .idea/sonarlint
182 |
183 | ### Java ###
184 | # Compiled class file
185 | *.class
186 |
187 | # Log file
188 |
189 | # BlueJ files
190 | *.ctxt
191 |
192 | # Mobile Tools for Java (J2ME)
193 | .mtj.tmp/
194 |
195 | # Package Files #
196 | *.war
197 | *.nar
198 | *.ear
199 | *.tar.gz
200 | *.rar
201 |
202 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
203 | hs_err_pid*
204 |
205 | ### Kotlin ###
206 | # Compiled class file
207 |
208 | # Log file
209 |
210 | # BlueJ files
211 |
212 | # Mobile Tools for Java (J2ME)
213 |
214 | # Package Files #
215 |
216 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
217 |
218 | ### Linux ###
219 | *~
220 |
221 | # temporary files which can be created if a process still has a handle open of a deleted file
222 | .fuse_hidden*
223 |
224 | # KDE directory preferences
225 | .directory
226 |
227 | # Linux trash folder which might appear on any partition or disk
228 | .Trash-*
229 |
230 | # .nfs files are created when an open file is removed but is still being accessed
231 | .nfs*
232 |
233 | ### macOS ###
234 | # General
235 | .DS_Store
236 | .AppleDouble
237 | .LSOverride
238 |
239 | # Icon must end with two \r
240 | Icon
241 |
242 | # Thumbnails
243 | ._*
244 |
245 | # Files that might appear in the root of a volume
246 | .DocumentRevisions-V100
247 | .fseventsd
248 | .Spotlight-V100
249 | .TemporaryItems
250 | .Trashes
251 | .VolumeIcon.icns
252 | .com.apple.timemachine.donotpresent
253 |
254 | # Directories potentially created on remote AFP share
255 | .AppleDB
256 | .AppleDesktop
257 | Network Trash Folder
258 | Temporary Items
259 | .apdisk
260 |
261 | ### Maven ###
262 | target/
263 | pom.xml.tag
264 | pom.xml.releaseBackup
265 | pom.xml.versionsBackup
266 | pom.xml.next
267 | release.properties
268 | dependency-reduced-pom.xml
269 | buildNumber.properties
270 | .mvn/timing.properties
271 | .mvn/wrapper/maven-wrapper.jar
272 |
273 | ### NetBeans ###
274 | **/nbproject/private/
275 | **/nbproject/Makefile-*.mk
276 | **/nbproject/Package-*.bash
277 | build/
278 | nbbuild/
279 | dist/
280 | nbdist/
281 | .nb-gradle/
282 |
283 | ### Node ###
284 | # Logs
285 | logs
286 | npm-debug.log*
287 | yarn-debug.log*
288 | yarn-error.log*
289 | lerna-debug.log*
290 |
291 | # Diagnostic reports (https://nodejs.org/api/report.html)
292 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
293 |
294 | # Runtime data
295 | pids
296 | *.pid
297 | *.seed
298 | *.pid.lock
299 |
300 | # Directory for instrumented libs generated by jscoverage/JSCover
301 | lib-cov
302 |
303 | # Coverage directory used by tools like istanbul
304 | coverage
305 |
306 | # nyc test coverage
307 | .nyc_output
308 |
309 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
310 | .grunt
311 |
312 | # Bower dependency directory (https://bower.io/)
313 | bower_components
314 |
315 | # node-waf configuration
316 | .lock-wscript
317 |
318 | # Compiled binary addons (https://nodejs.org/api/addons.html)
319 | build/Release
320 |
321 | # Dependency directories
322 | node_modules/
323 | jspm_packages/
324 |
325 | # TypeScript v1 declaration files
326 | typings/
327 |
328 | # Optional npm cache directory
329 | .npm
330 |
331 | # Optional eslint cache
332 | .eslintcache
333 |
334 | # Optional REPL history
335 | .node_repl_history
336 |
337 | # Output of 'npm pack'
338 |
339 | # Yarn Integrity file
340 | .yarn-integrity
341 |
342 | # dotenv environment variables file
343 | .env
344 | .env.test
345 |
346 | # parcel-bundler cache (https://parceljs.org/)
347 | .cache
348 |
349 | # next.js build output
350 | .next
351 |
352 | # nuxt.js build output
353 | .nuxt
354 |
355 | # vuepress build output
356 | .vuepress/dist
357 |
358 | # Serverless directories
359 | .serverless/
360 |
361 | # FuseBox cache
362 | .fusebox/
363 |
364 | # DynamoDB Local files
365 | .dynamodb/
366 |
367 | ### Vagrant ###
368 | # General
369 | .vagrant/
370 |
371 | # Log files (if you are creating logs in debug mode, uncomment this)
372 | # *.logs
373 |
374 | ### Vagrant Patch ###
375 | *.box
376 |
377 | ### Web ###
378 | *.asp
379 | *.cer
380 | *.csr
381 | *.css
382 | *.htm
383 | *.html
384 | *.js
385 | *.jsp
386 | *.php
387 | *.rss
388 | *.xhtml
389 |
390 | ### Gradle ###
391 | .gradle
392 |
393 | # Ignore Gradle GUI config
394 | gradle-app.setting
395 |
396 | # Avoid ignoring Gradle wrapper jar file (.jar files are usually ignored)
397 | !gradle-wrapper.jar
398 |
399 | # Cache of project
400 | .gradletasknamecache
401 |
402 | # # Work around https://youtrack.jetbrains.com/issue/IDEA-116898
403 | # gradle/wrapper/gradle-wrapper.properties
404 |
405 | ### Gradle Patch ###
406 | **/build/
407 |
408 | # End of https://www.gitignore.io/api/web,git,node,java,maven,macos,linux,gradle,kotlin,eclipse,vagrant,netbeans,intellij,archlinuxpackages
409 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: java
2 |
3 | jdk:
4 | - openjdk10
5 |
6 | install: ./gradlew clean build
7 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 | #### [](https://travis-ci.com/braintelligencePL/project-manager-kotlin) 🛠
3 |
4 | ### [Project-Manager - just like trello but only backend](https://github.com/braintelligencePL/project-manager-kotlin)
5 | #### Journey from layered (n-tier) architecture to hexagonal architecture in Kotlin 💪
6 | Project-Manager is a simple application for managing projects at company.
7 | You can create projects and teams.
8 | Projects can have features and status.
9 | Teams can have members and projects.
10 | You'll find more business requirments below.
11 |
12 | We'll go from traditional layered architecture to hexagonal architecture A.K.A. Ports and Adapters architecture.
13 |
14 |
15 |
16 | ### **There is also newer repository with similar architecture style:**
17 |
18 | #### [Online Store - clean architecture](https://github.com/braintelligencePL/online-store-clean-architecture)
19 |
20 |
21 |
22 | # Quick Start
23 |
24 | ### Working with Project-Manager
25 |
26 | `./gradlew bootRun` - to run application.
27 | `./gradlew test` - to run unit tests.
28 | `./gradlew clean build test`- one to rule them all 💍
29 |
30 |
31 | Start with [endpoints](https://github.com/braintelligencePL/project-manager-kotlin/tree/master/src/main/kotlin/pl/braintelligence/projectmanager/infrastructure/adapter/incoming/rest).
32 | After that check [tests](https://github.com/braintelligencePL/project-manager-kotlin/tree/master/src/test/groovy/pl/braintelligence/projectmanager). Whole domain is tested with unit tests. Isolated from controllers, database, framework. Tests are done with a use of repository implemented as HashMap. You also have [AcceptanceTests](https://github.com/braintelligencePL/project-manager-kotlin/blob/master/src/test/groovy/pl/braintelligence/projectmanager/project/ProjectAcceptanceTest.groovy) that show user flow, bigger picture.
33 |
34 |
35 |
36 | ## Implementation step-by-step
37 |
38 | Idea is to see how does project changes while time passes. Each branch has some changes either refactor or new features implemented.
39 |
40 |
41 |
42 | ### 1️⃣ `branch: step-1-team`
43 | 🏠 **Architecture**: Layered Architecure
44 |
45 | * [x] `POST: /teams` - create a team.
46 | * [x] `POST: /teams/:teamName/members` - add members to the team.
47 | * [x] `GET: /teams` - show teams.
48 |
49 | Needs and constraints:
50 | * Team cannot be created if already exists
51 | * How many projects team has?
52 |
53 |
54 |
55 |
56 | ### 2️⃣ `branch: step-2-projects`
57 | 🏠 **Architecture**: Layered Architecure
58 |
59 | * [x] `POST: /projects/drafts` - create project draft (only project name).
60 | * [x] `POST: /projects` - create project with features. 📊
61 | * [x] `GET: /projects` - show draft projects
62 |
63 | Needs and constraints:
64 | * JobPosition must be valid (Developer, Scrum Master...)
65 | * Team can have no more than 3 projects at the time
66 |
67 |
68 |
69 | ### 3️⃣ `branch: step-3-refactor`
70 | 🏠 **Architecture**: Hexagonal Architecure
71 |
72 | Things done:
73 |
74 | * Moving from layered (n-tier) architecture to Hexagonal Architecture (ports and adapters). 😎
75 | * Introduced idea of shared-kernel from DDD
76 |
77 | Improved tests:
78 |
79 | * Unit tests without touching IO. Domain is tested with unit tests. Idea of `InMemoryRepository` as HashMap.
80 | * Acceptance tests show flow of the app or bounded-context.
81 | * Integration tests are only for most important business value paths because whole domain is tested with unit tests.
82 |
83 |
84 |
85 | ### 4️⃣ `branch: step-4-projects`
86 | 🏠 **Architecture**: Hexagonal Architecure
87 |
88 | * [ ] `GET: /projects/:id` - show project
89 | * [ ] `GET: /projects` - show projects
90 | * [ ] `PUT: /projects/:id` - change/update project
91 | * [ ] `PATCH: /projects/:id/started` - start project when team assigned
92 | * [ ] `PATCH: /projects/:id/ended` - close project when features are done
93 |
94 | Needs and constraints:
95 | * No `if` statements! We can do better in Kotlin. Not something that you should avoid at any cost (its just a kata).
96 | * Project status and feature status -> `Status` must be valid (TO_DO, IN_PROGRESS...)
97 | * `PriorityLevel` for project features must be valid (HIGH, MEDIUM, NOT_DEFINED...)
98 |
99 |
100 |
101 | ### #️⃣ `branch: will-be-more`
102 | - Refactor introducing simple CQRS.
103 | - monitoring, grafana, actuator, performance tests with gatling
104 |
105 | `branch: step-X-zoo-of-microservices`
106 |
107 | 🏠 **Architecture**: Hexagonal Architecture (modularization on package level)
108 | 🕳 **Tests**: Integration/Acceptance/Unit
109 | Backing-Services from [Twelve-Factor-App](https://12factor.net/) methodology.
110 |
111 | Services from our zoo:
112 | 🦓 **user-autorization-service** - authentication gateway that protects back-end resources. There is two kinds of resources protected and unprotected. First one requires user-level authentication and second one is just read-only such as listing of offers/products.
113 | 🐼 **edge-service** - gives possibility to expose unified REST API from all of ours backend services. To be able to do this, the Edge Service matches a request route’s URL fragment from a front-end application to a back-end microservice through a reverse proxy to retrieve the remote REST API response.
114 | 🐰 **discovery-service** - Edge-service matches a request route’s URL fragment from a front-end application to a back-end microservice through a reverse proxy to retrieve the remote REST API response.
115 | 🐿 **centralized-configuration-server** - Spring Cloud application that centralizes external configurations using various methodologies of [building twelve-factor applications](https://12factor.net/config).
116 |
117 |
118 |
119 | ## Technologies used:
120 | - Kotlin with spring
121 | - Spock (groovy) for tests
122 | - ArchUnit (kotlin) for architecture tests
123 | - Gradle to build project
124 |
125 | ### Materials from mine blog:
126 | * PL: [Prawie trywialna aplikacja do zarządzania projektami](http://braintelligence.pl/prawie-trywialna-aplikacja-do-zarzadzania-projektami) - bardziej szczegółowy opis projektu.
127 | * ENG: [ The nature of domain driven design](http://www.braintelligence.pl/the-nature-of-domain-driven-design/) - about DDD strategic tools.
128 |
129 | ### Materials from outside world:
130 | * ENG: [ ddd-workshop-project-manager (business requirments from this repo)](https://github.com/mkopylec/project-manager)
131 | * ENG: [ example of hexagonal architecture (on package level)](https://github.com/jakubnabrdalik/hentai)
132 | * ENG: [ design patterns for humans ](https://github.com/kamranahmedse/design-patterns-for-humans)
133 | * ENG: [ ddd-laeven ](https://github.com/BottegaIT/ddd-leaven-v2)
134 | * ENG: [ awsome-ddd ](https://github.com/heynickc/awesome-ddd)
135 | * ENG: [ twelve-factor-app - methodology for building software-as-a-service](https://12factor.net/)
136 |
--------------------------------------------------------------------------------
/build.gradle:
--------------------------------------------------------------------------------
1 | buildscript {
2 | ext {
3 | kotlinVersion = '1.3.21'
4 | springBootVersion = '2.1.2.RELEASE'
5 | }
6 | repositories {
7 | mavenCentral()
8 | }
9 | dependencies {
10 | classpath "org.springframework.boot:spring-boot-gradle-plugin:$springBootVersion"
11 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlinVersion"
12 | // classpath "org.jetbrains.kotlin:kotlin-allopen:$kotlinVersion"
13 | }
14 | }
15 |
16 | apply plugin: 'idea'
17 | apply plugin: 'kotlin'
18 | apply plugin: 'groovy'
19 | //apply plugin: 'kotlin-spring'
20 | apply plugin: 'org.springframework.boot'
21 | apply plugin: 'io.spring.dependency-management'
22 |
23 | repositories {
24 | jcenter()
25 | mavenCentral()
26 | maven { url 'https://jitpack.io' } // for Spock 1.3
27 | }
28 |
29 | group = 'pl.braintelligence'
30 | sourceCompatibility = JavaVersion.VERSION_1_10
31 | targetCompatibility = JavaVersion.VERSION_1_10
32 |
33 | dependencies {
34 | // Kotlin
35 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8"
36 | implementation "org.jetbrains.kotlin:kotlin-reflect"
37 | implementation "com.fasterxml.jackson.module:jackson-module-kotlin:2.9.+"
38 | implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.1.1'
39 | implementation 'de.flapdoodle.embed:de.flapdoodle.embed.mongo:2.2.0'
40 | implementation 'io.arrow-kt:arrow-core:0.8.2'
41 |
42 | // Spring
43 | implementation 'org.springframework.boot:spring-boot-starter-actuator'
44 | implementation 'org.springframework.boot:spring-boot-starter-web'
45 | implementation "org.springframework.boot:spring-boot-starter-data-mongodb"
46 |
47 | // Commons, utils
48 | implementation 'org.apache.commons:commons-lang3:3.7'
49 | implementation 'org.apache.commons:commons-collections4:4.1'
50 | implementation 'org.apache.httpcomponents:httpclient:4.5.6'
51 | implementation "io.vavr:vavr:0.9.3"
52 |
53 | // Tests
54 | testImplementation 'org.spockframework:spock-core:1.3-groovy-2.5'
55 | testImplementation 'org.spockframework:spock-spring:1.3-groovy-2.5'
56 | testImplementation 'org.springframework.boot:spring-boot-starter-test'
57 |
58 | testImplementation 'com.github.tomakehurst:wiremock:2.21.0'
59 | testImplementation 'com.tngtech.archunit:archunit:0.10.1'
60 |
61 | testImplementation 'com.tngtech.archunit:archunit-junit5-api:0.9.3'
62 | testImplementation 'com.tngtech.archunit:archunit-junit5-engine:0.9.3'
63 | testImplementation 'org.junit.jupiter:junit-jupiter-api'
64 | testImplementation 'org.junit.jupiter:junit-jupiter-params'
65 | testImplementation group: 'org.awaitility', name: 'awaitility-groovy', version: '3.1.0'
66 | testRuntime 'org.junit.jupiter:junit-jupiter-engine'
67 | testRuntime 'org.junit.platform:junit-platform-engine'
68 | }
69 |
--------------------------------------------------------------------------------
/build.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env sh
2 |
3 | ./gradlew clean check assemble
4 | docker build -t pl.braintelligence -f docker/prometheus/Dockerfile .
5 | docker build -t pl.braintelligence -f docker/app/Dockerfile .
6 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/braintelligencePL/project-manager-kotlin/21164747c0e639b3d9ef30ed9e6ff445ec31d260/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Sun Mar 31 09:19:24 CEST 2019
2 | distributionBase=GRADLE_USER_HOME
3 | distributionPath=wrapper/dists
4 | zipStoreBase=GRADLE_USER_HOME
5 | zipStorePath=wrapper/dists
6 | distributionUrl=https\://services.gradle.org/distributions/gradle-5.3.1-all.zip
7 |
--------------------------------------------------------------------------------
/gradlew:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env sh
2 |
3 | #
4 | # Copyright 2015 the original author or authors.
5 | #
6 | # Licensed under the Apache License, Version 2.0 (the "License");
7 | # you may not use this file except in compliance with the License.
8 | # You may obtain a copy of the License at
9 | #
10 | # http://www.apache.org/licenses/LICENSE-2.0
11 | #
12 | # Unless required by applicable law or agreed to in writing, software
13 | # distributed under the License is distributed on an "AS IS" BASIS,
14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 | # See the License for the specific language governing permissions and
16 | # limitations under the License.
17 | #
18 |
19 | ##############################################################################
20 | ##
21 | ## Gradle start up script for UN*X
22 | ##
23 | ##############################################################################
24 |
25 | # Attempt to set APP_HOME
26 | # Resolve links: $0 may be a link
27 | PRG="$0"
28 | # Need this for relative symlinks.
29 | while [ -h "$PRG" ] ; do
30 | ls=`ls -ld "$PRG"`
31 | link=`expr "$ls" : '.*-> \(.*\)$'`
32 | if expr "$link" : '/.*' > /dev/null; then
33 | PRG="$link"
34 | else
35 | PRG=`dirname "$PRG"`"/$link"
36 | fi
37 | done
38 | SAVED="`pwd`"
39 | cd "`dirname \"$PRG\"`/" >/dev/null
40 | APP_HOME="`pwd -P`"
41 | cd "$SAVED" >/dev/null
42 |
43 | APP_NAME="Gradle"
44 | APP_BASE_NAME=`basename "$0"`
45 |
46 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
47 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
48 |
49 | # Use the maximum available, or set MAX_FD != -1 to use that value.
50 | MAX_FD="maximum"
51 |
52 | warn () {
53 | echo "$*"
54 | }
55 |
56 | die () {
57 | echo
58 | echo "$*"
59 | echo
60 | exit 1
61 | }
62 |
63 | # OS specific support (must be 'true' or 'false').
64 | cygwin=false
65 | msys=false
66 | darwin=false
67 | nonstop=false
68 | case "`uname`" in
69 | CYGWIN* )
70 | cygwin=true
71 | ;;
72 | Darwin* )
73 | darwin=true
74 | ;;
75 | MINGW* )
76 | msys=true
77 | ;;
78 | NONSTOP* )
79 | nonstop=true
80 | ;;
81 | esac
82 |
83 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
84 |
85 | # Determine the Java command to use to start the JVM.
86 | if [ -n "$JAVA_HOME" ] ; then
87 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
88 | # IBM's JDK on AIX uses strange locations for the executables
89 | JAVACMD="$JAVA_HOME/jre/sh/java"
90 | else
91 | JAVACMD="$JAVA_HOME/bin/java"
92 | fi
93 | if [ ! -x "$JAVACMD" ] ; then
94 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
95 |
96 | Please set the JAVA_HOME variable in your environment to match the
97 | location of your Java installation."
98 | fi
99 | else
100 | JAVACMD="java"
101 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
102 |
103 | Please set the JAVA_HOME variable in your environment to match the
104 | location of your Java installation."
105 | fi
106 |
107 | # Increase the maximum file descriptors if we can.
108 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
109 | MAX_FD_LIMIT=`ulimit -H -n`
110 | if [ $? -eq 0 ] ; then
111 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
112 | MAX_FD="$MAX_FD_LIMIT"
113 | fi
114 | ulimit -n $MAX_FD
115 | if [ $? -ne 0 ] ; then
116 | warn "Could not set maximum file descriptor limit: $MAX_FD"
117 | fi
118 | else
119 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
120 | fi
121 | fi
122 |
123 | # For Darwin, add options to specify how the application appears in the dock
124 | if $darwin; then
125 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
126 | fi
127 |
128 | # For Cygwin, switch paths to Windows format before running java
129 | if $cygwin ; then
130 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
131 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
132 | JAVACMD=`cygpath --unix "$JAVACMD"`
133 |
134 | # We build the pattern for arguments to be converted via cygpath
135 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
136 | SEP=""
137 | for dir in $ROOTDIRSRAW ; do
138 | ROOTDIRS="$ROOTDIRS$SEP$dir"
139 | SEP="|"
140 | done
141 | OURCYGPATTERN="(^($ROOTDIRS))"
142 | # Add a user-defined pattern to the cygpath arguments
143 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then
144 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
145 | fi
146 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
147 | i=0
148 | for arg in "$@" ; do
149 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
150 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
151 |
152 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
153 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
154 | else
155 | eval `echo args$i`="\"$arg\""
156 | fi
157 | i=$((i+1))
158 | done
159 | case $i in
160 | (0) set -- ;;
161 | (1) set -- "$args0" ;;
162 | (2) set -- "$args0" "$args1" ;;
163 | (3) set -- "$args0" "$args1" "$args2" ;;
164 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
165 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
166 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
167 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
168 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
169 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
170 | esac
171 | fi
172 |
173 | # Escape application args
174 | save () {
175 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
176 | echo " "
177 | }
178 | APP_ARGS=$(save "$@")
179 |
180 | # Collect all arguments for the java command, following the shell quoting and substitution rules
181 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
182 |
183 | # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
184 | if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
185 | cd "$(dirname "$0")"
186 | fi
187 |
188 | exec "$JAVACMD" "$@"
189 |
--------------------------------------------------------------------------------
/gradlew.bat:
--------------------------------------------------------------------------------
1 | @rem
2 | @rem Copyright 2015 the original author or authors.
3 | @rem
4 | @rem Licensed under the Apache License, Version 2.0 (the "License");
5 | @rem you may not use this file except in compliance with the License.
6 | @rem You may obtain a copy of the License at
7 | @rem
8 | @rem http://www.apache.org/licenses/LICENSE-2.0
9 | @rem
10 | @rem Unless required by applicable law or agreed to in writing, software
11 | @rem distributed under the License is distributed on an "AS IS" BASIS,
12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | @rem See the License for the specific language governing permissions and
14 | @rem limitations under the License.
15 | @rem
16 |
17 | @if "%DEBUG%" == "" @echo off
18 | @rem ##########################################################################
19 | @rem
20 | @rem Gradle startup script for Windows
21 | @rem
22 | @rem ##########################################################################
23 |
24 | @rem Set local scope for the variables with windows NT shell
25 | if "%OS%"=="Windows_NT" setlocal
26 |
27 | set DIRNAME=%~dp0
28 | if "%DIRNAME%" == "" set DIRNAME=.
29 | set APP_BASE_NAME=%~n0
30 | set APP_HOME=%DIRNAME%
31 |
32 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
33 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
34 |
35 | @rem Find java.exe
36 | if defined JAVA_HOME goto findJavaFromJavaHome
37 |
38 | set JAVA_EXE=java.exe
39 | %JAVA_EXE% -version >NUL 2>&1
40 | if "%ERRORLEVEL%" == "0" goto init
41 |
42 | echo.
43 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
44 | echo.
45 | echo Please set the JAVA_HOME variable in your environment to match the
46 | echo location of your Java installation.
47 |
48 | goto fail
49 |
50 | :findJavaFromJavaHome
51 | set JAVA_HOME=%JAVA_HOME:"=%
52 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
53 |
54 | if exist "%JAVA_EXE%" goto init
55 |
56 | echo.
57 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
58 | echo.
59 | echo Please set the JAVA_HOME variable in your environment to match the
60 | echo location of your Java installation.
61 |
62 | goto fail
63 |
64 | :init
65 | @rem Get command-line arguments, handling Windows variants
66 |
67 | if not "%OS%" == "Windows_NT" goto win9xME_args
68 |
69 | :win9xME_args
70 | @rem Slurp the command line arguments.
71 | set CMD_LINE_ARGS=
72 | set _SKIP=2
73 |
74 | :win9xME_args_slurp
75 | if "x%~1" == "x" goto execute
76 |
77 | set CMD_LINE_ARGS=%*
78 |
79 | :execute
80 | @rem Setup the command line
81 |
82 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
83 |
84 | @rem Execute Gradle
85 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
86 |
87 | :end
88 | @rem End local scope for the variables with windows NT shell
89 | if "%ERRORLEVEL%"=="0" goto mainEnd
90 |
91 | :fail
92 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
93 | rem the _cmd.exe /c_ return code!
94 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
95 | exit /b 1
96 |
97 | :mainEnd
98 | if "%OS%"=="Windows_NT" endlocal
99 |
100 | :omega
101 |
--------------------------------------------------------------------------------
/settings.gradle:
--------------------------------------------------------------------------------
1 | rootProject.name = 'project-manager-kotlin'
--------------------------------------------------------------------------------
/src/main/kotlin/pl/braintelligence/projectmanager/Application.kt:
--------------------------------------------------------------------------------
1 | package pl.braintelligence.projectmanager
2 |
3 | import org.slf4j.Logger
4 | import org.slf4j.LoggerFactory
5 | import org.springframework.boot.SpringApplication
6 | import org.springframework.boot.autoconfigure.SpringBootApplication
7 | import kotlin.reflect.full.companionObject
8 |
9 |
10 | @SpringBootApplication
11 | open class Application
12 |
13 | fun main(args: Array) {
14 | SpringApplication.run(Application::class.java, *args)
15 | }
16 |
17 | fun R.logger(): Lazy = lazy { LoggerFactory.getLogger(unwrapCompanionClass(this.javaClass).name) }
18 |
19 | fun unwrapCompanionClass(ofClass: Class): Class<*> =
20 | if (ofClass.enclosingClass?.kotlin?.companionObject?.java == ofClass) {
21 | ofClass.enclosingClass
22 | } else {
23 | ofClass
24 | }
25 |
--------------------------------------------------------------------------------
/src/main/kotlin/pl/braintelligence/projectmanager/core/projects/domain/Feature.kt:
--------------------------------------------------------------------------------
1 | package pl.braintelligence.projectmanager.core.projects.domain
2 |
3 | class Feature(
4 | val name: String,
5 | val status: Status = Status.TO_DO,
6 | val priorityLevel: PriorityLevel = PriorityLevel.NOT_DEFINED
7 | )
8 |
--------------------------------------------------------------------------------
/src/main/kotlin/pl/braintelligence/projectmanager/core/projects/domain/PriorityLevel.kt:
--------------------------------------------------------------------------------
1 | package pl.braintelligence.projectmanager.core.projects.domain
2 |
3 | enum class PriorityLevel {
4 | HIGHEST,
5 | HIGH,
6 | MEDIUM,
7 | LOW,
8 | LOWEST,
9 | NOT_DEFINED
10 | }
11 |
--------------------------------------------------------------------------------
/src/main/kotlin/pl/braintelligence/projectmanager/core/projects/domain/Project.kt:
--------------------------------------------------------------------------------
1 | package pl.braintelligence.projectmanager.core.projects.domain
2 |
3 | data class Project(
4 | val id: String,
5 | val name: String,
6 | val status: Status = Status.TO_DO,
7 | val teamAssigned: String = "",
8 | val features: List = listOf()
9 | )
10 |
--------------------------------------------------------------------------------
/src/main/kotlin/pl/braintelligence/projectmanager/core/projects/domain/ProjectCreatorService.kt:
--------------------------------------------------------------------------------
1 | package pl.braintelligence.projectmanager.core.projects.domain
2 |
3 | import org.springframework.stereotype.Service
4 | import pl.braintelligence.projectmanager.core.projects.ports.incoming.ProjectCreatorPort
5 | import pl.braintelligence.projectmanager.core.projects.ports.outgoing.ProjectCreatorRepository
6 | import pl.braintelligence.projectmanager.infrastructure.adapter.incoming.rest.dto.ProjectDraft
7 | import pl.braintelligence.projectmanager.infrastructure.adapter.incoming.rest.dto.ProjectWithFeatures
8 |
9 | @Service
10 | class ProjectCreatorService(
11 | private val projectFactory: ProjectFactory,
12 | private val projectCreatorRepository: ProjectCreatorRepository
13 | ) : ProjectCreatorPort {
14 |
15 | override fun createProjectDraft(projectDraft: ProjectDraft): Project =
16 | projectFactory.createProjectDraft(projectDraft.projectName)
17 | .also { projectCreatorRepository.save(it) }
18 |
19 | override fun createProjectWithFeatures(projectWithFeatures: ProjectWithFeatures): Project =
20 | projectFactory.createProjectWithFeatures(projectWithFeatures)
21 | .also { projectCreatorRepository.save(it) }
22 |
23 | }
24 |
--------------------------------------------------------------------------------
/src/main/kotlin/pl/braintelligence/projectmanager/core/projects/domain/ProjectExceptions.kt:
--------------------------------------------------------------------------------
1 | package pl.braintelligence.projectmanager.core.projects.domain
2 |
3 | import pl.braintelligence.projectmanager.shared.DomainException
4 |
5 |
6 | internal class MissingProjectException(message: String) : DomainException(message)
7 |
--------------------------------------------------------------------------------
/src/main/kotlin/pl/braintelligence/projectmanager/core/projects/domain/ProjectFactory.kt:
--------------------------------------------------------------------------------
1 | package pl.braintelligence.projectmanager.core.projects.domain
2 |
3 | import org.springframework.stereotype.Component
4 | import pl.braintelligence.projectmanager.infrastructure.adapter.incoming.rest.dto.ProjectWithFeatures
5 | import java.util.*
6 |
7 | @Component
8 | open class ProjectFactory {
9 |
10 | fun createProjectDraft(projectName: String): Project {
11 | val id = generateProjectUniqueId()
12 | return Project(id = id, name = projectName)
13 | }
14 |
15 | fun createProjectWithFeatures(projectWithFeatures: ProjectWithFeatures): Project {
16 | val id = generateProjectUniqueId()
17 | val name = projectWithFeatures.projectName
18 | val features = projectWithFeatures.features
19 |
20 | return Project(id = id, name = name, features = features)
21 | }
22 |
23 |
24 | private fun generateProjectUniqueId() = UUID.randomUUID().toString()
25 | }
26 |
--------------------------------------------------------------------------------
/src/main/kotlin/pl/braintelligence/projectmanager/core/projects/domain/ProjectQueryService.kt:
--------------------------------------------------------------------------------
1 | package pl.braintelligence.projectmanager.core.projects.domain
2 |
3 | import org.springframework.stereotype.Service
4 | import pl.braintelligence.projectmanager.core.projects.ports.incoming.ProjectQueryPort
5 | import pl.braintelligence.projectmanager.core.projects.ports.outgoing.ProjectQueryRepository
6 |
7 | @Service
8 | class ProjectQueryService(
9 | private val projectQueryRepository: ProjectQueryRepository
10 | ) : ProjectQueryPort {
11 |
12 | override fun getProject(id: String): Project =
13 | projectQueryRepository.findById(id)
14 | ?: throw MissingProjectException("Project does not exist.")
15 |
16 | override fun getProjects(): List {
17 | TODO("not implemented")
18 | }
19 |
20 |
21 | }
22 |
--------------------------------------------------------------------------------
/src/main/kotlin/pl/braintelligence/projectmanager/core/projects/domain/Status.kt:
--------------------------------------------------------------------------------
1 | package pl.braintelligence.projectmanager.core.projects.domain
2 |
3 | enum class Status {
4 | TO_DO,
5 | IN_PROGRESS,
6 | COMPLETED
7 | }
8 |
--------------------------------------------------------------------------------
/src/main/kotlin/pl/braintelligence/projectmanager/core/projects/domain/configuration/InMemoryProjectRepository.kt:
--------------------------------------------------------------------------------
1 | package pl.braintelligence.projectmanager.core.projects.domain.configuration
2 |
3 | import pl.braintelligence.projectmanager.core.projects.domain.Project
4 | import pl.braintelligence.projectmanager.core.projects.ports.outgoing.ProjectCreatorRepository
5 | import pl.braintelligence.projectmanager.core.projects.ports.outgoing.ProjectQueryRepository
6 | import pl.braintelligence.projectmanager.shared.InMemoryCrudRepository
7 |
8 | open class InMemoryProjectRepository : InMemoryCrudRepository(), ProjectCreatorRepository, ProjectQueryRepository {
9 |
10 | override fun save(project: Project) {
11 | super.save(entity = project, id = project.id)
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/src/main/kotlin/pl/braintelligence/projectmanager/core/projects/domain/configuration/TeamConfiguration.kt:
--------------------------------------------------------------------------------
1 | package pl.braintelligence.projectmanager.core.projects.domain.configuration
2 |
3 | import org.springframework.context.annotation.Bean
4 | import org.springframework.context.annotation.Configuration
5 | import pl.braintelligence.projectmanager.core.projects.domain.ProjectCreatorService
6 | import pl.braintelligence.projectmanager.core.projects.domain.ProjectFactory
7 | import pl.braintelligence.projectmanager.core.projects.domain.ProjectQueryService
8 | import pl.braintelligence.projectmanager.core.projects.ports.incoming.ProjectCreatorPort
9 | import pl.braintelligence.projectmanager.core.projects.ports.incoming.ProjectQueryPort
10 | import pl.braintelligence.projectmanager.core.projects.ports.outgoing.ProjectCreatorRepository
11 | import pl.braintelligence.projectmanager.core.projects.ports.outgoing.ProjectQueryRepository
12 |
13 | @Configuration
14 | open class ProjectConfiguration {
15 |
16 | open fun buildProjectCreator(
17 | projectFactory: ProjectFactory,
18 | inMemoryProjectRepository: InMemoryProjectRepository
19 | ): ProjectCreatorPort =
20 | ProjectCreatorService(projectFactory, inMemoryProjectRepository)
21 |
22 | @Bean
23 | open fun buildProjectCreator(
24 | projectFactory: ProjectFactory,
25 | projectCreatorRepository: ProjectCreatorRepository
26 | ): ProjectCreatorPort =
27 | ProjectCreatorService(projectFactory, projectCreatorRepository)
28 |
29 |
30 | open fun buildProjectQuery(
31 | inMemoryProjectRepository: InMemoryProjectRepository
32 | ): ProjectQueryPort =
33 | ProjectQueryService(inMemoryProjectRepository)
34 |
35 | @Bean
36 | open fun buildProjectQuery(
37 | projectQueryRepository: ProjectQueryRepository
38 | ): ProjectQueryPort =
39 | ProjectQueryService(projectQueryRepository)
40 |
41 |
42 | }
43 |
--------------------------------------------------------------------------------
/src/main/kotlin/pl/braintelligence/projectmanager/core/projects/ports/incoming/ProjectCreatorPort.kt:
--------------------------------------------------------------------------------
1 | package pl.braintelligence.projectmanager.core.projects.ports.incoming
2 |
3 | import pl.braintelligence.projectmanager.core.projects.domain.Project
4 | import pl.braintelligence.projectmanager.infrastructure.adapter.incoming.rest.dto.ProjectDraft
5 | import pl.braintelligence.projectmanager.infrastructure.adapter.incoming.rest.dto.ProjectWithFeatures
6 |
7 | interface ProjectCreatorPort {
8 |
9 | fun createProjectDraft(projectDraft: ProjectDraft): Project
10 |
11 | fun createProjectWithFeatures(projectWithFeatures: ProjectWithFeatures): Project
12 |
13 | }
14 |
--------------------------------------------------------------------------------
/src/main/kotlin/pl/braintelligence/projectmanager/core/projects/ports/incoming/ProjectQueryPort.kt:
--------------------------------------------------------------------------------
1 | package pl.braintelligence.projectmanager.core.projects.ports.incoming
2 |
3 | import pl.braintelligence.projectmanager.core.projects.domain.Project
4 |
5 | interface ProjectQueryPort {
6 |
7 | fun getProject(id: String): Project
8 |
9 | fun getProjects(): List
10 | }
11 |
--------------------------------------------------------------------------------
/src/main/kotlin/pl/braintelligence/projectmanager/core/projects/ports/outgoing/ProjectCreatorRepository.kt:
--------------------------------------------------------------------------------
1 | package pl.braintelligence.projectmanager.core.projects.ports.outgoing
2 |
3 | import pl.braintelligence.projectmanager.core.projects.domain.Project
4 |
5 | interface ProjectCreatorRepository {
6 |
7 | fun save(project: Project)
8 |
9 | }
10 |
--------------------------------------------------------------------------------
/src/main/kotlin/pl/braintelligence/projectmanager/core/projects/ports/outgoing/ProjectQueryRepository.kt:
--------------------------------------------------------------------------------
1 | package pl.braintelligence.projectmanager.core.projects.ports.outgoing
2 |
3 | import pl.braintelligence.projectmanager.core.projects.domain.Project
4 |
5 | interface ProjectQueryRepository {
6 |
7 | fun findById(id: String): Project?
8 |
9 | }
10 |
--------------------------------------------------------------------------------
/src/main/kotlin/pl/braintelligence/projectmanager/core/team/domain/Employee.kt:
--------------------------------------------------------------------------------
1 | package pl.braintelligence.projectmanager.core.team.domain
2 |
3 | import arrow.core.Try
4 | import arrow.core.getOrElse
5 | import pl.braintelligence.projectmanager.infrastructure.adapter.incoming.rest.dto.TeamMember
6 |
7 | class Employee(
8 | val firstName: String,
9 | val lastName: String,
10 | val jobPosition: JobPosition
11 | ) {
12 | fun hasNoFirstName(): Boolean = firstName.isNotBlank()
13 | fun hasNoLastName(): Boolean = lastName.isNotBlank()
14 | fun hasInvalidJobPosition(): Boolean = jobPosition.isValid()
15 |
16 | companion object {
17 | fun mapToEmployee(teamMember: TeamMember): Employee =
18 | Employee(
19 | teamMember.firstName,
20 | teamMember.lastName,
21 | toJobPosition(teamMember.jobPosition)
22 | )
23 |
24 | private fun toJobPosition(jobPosition: String) =
25 | Try { JobPosition.valueOf(jobPosition) }
26 | .getOrElse { JobPosition.INVALID }
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/src/main/kotlin/pl/braintelligence/projectmanager/core/team/domain/JobPosition.kt:
--------------------------------------------------------------------------------
1 | package pl.braintelligence.projectmanager.core.team.domain
2 |
3 | enum class JobPosition {
4 | SCRUM_MASTER,
5 | DEVELOPER,
6 | PRODUCT_OWNER,
7 | INVALID;
8 |
9 | fun isValid(): Boolean {
10 | return this != INVALID
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/src/main/kotlin/pl/braintelligence/projectmanager/core/team/domain/Team.kt:
--------------------------------------------------------------------------------
1 | package pl.braintelligence.projectmanager.core.team.domain
2 |
3 | data class Team @JvmOverloads constructor(
4 | val name: String,
5 | val numberOfOngoingProjects: Int = 0,
6 | var members: List = listOf()
7 | ) {
8 | init {
9 | require(name.isNotBlank()) { throw InvalidTeamException("Empty team name.") }
10 | }
11 |
12 | fun addMember(teamMember: Employee) {
13 | validateMember(teamMember)
14 | members = members.plus(teamMember)
15 | }
16 |
17 | fun isTeamBusy(): Boolean = numberOfOngoingProjects > BUSY_TEAM_THRESHOLD
18 |
19 | private fun validateMember(teamMember: Employee) {
20 | require(teamMember.hasNoFirstName()) { throw InvalidTeamMemberException("Empty member first name.") }
21 | require(teamMember.hasNoLastName()) { throw InvalidTeamMemberException("Empty member last name.") }
22 | require(teamMember.hasInvalidJobPosition()) { throw InvalidTeamMemberException("Invalid job position.") }
23 | }
24 |
25 | companion object {
26 | const val BUSY_TEAM_THRESHOLD = 3
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/src/main/kotlin/pl/braintelligence/projectmanager/core/team/domain/TeamExceptions.kt:
--------------------------------------------------------------------------------
1 | package pl.braintelligence.projectmanager.core.team.domain
2 |
3 | import pl.braintelligence.projectmanager.shared.DomainException
4 |
5 |
6 | internal class EntityAlreadyExistsException(message: String) : DomainException(message)
7 |
8 | internal class MissingTeamException(message: String) : DomainException(message)
9 |
10 | internal class InvalidTeamException(message: String) : DomainException(message)
11 |
12 | internal class InvalidTeamMemberException(message: String) : DomainException(message)
13 |
--------------------------------------------------------------------------------
/src/main/kotlin/pl/braintelligence/projectmanager/core/team/domain/TeamFacade.kt:
--------------------------------------------------------------------------------
1 | package pl.braintelligence.projectmanager.core.team.domain
2 |
3 | import org.springframework.stereotype.Service
4 | import pl.braintelligence.projectmanager.core.team.ports.incoming.TeamManager
5 | import pl.braintelligence.projectmanager.core.team.ports.outgoing.TeamRepository
6 | import pl.braintelligence.projectmanager.infrastructure.adapter.incoming.rest.dto.NewTeam
7 | import pl.braintelligence.projectmanager.infrastructure.adapter.incoming.rest.dto.TeamMember
8 |
9 | @Service
10 | class TeamFacade(
11 | private val teamRepository: TeamRepository
12 | ) : TeamManager {
13 |
14 | override fun createTeam(newTeam: NewTeam) = when (teamRepository.existsByName(newTeam.name)) {
15 | true -> throw EntityAlreadyExistsException("Team already exist.")
16 | false -> teamRepository.save(Team(name = newTeam.name))
17 | }
18 |
19 | override fun addMemberToTeam(teamName: String, teamMember: TeamMember) {
20 | val team = getTeam(teamName)
21 |
22 | Employee.mapToEmployee(teamMember)
23 | .apply { team.addMember(this) }
24 | .also { teamRepository.save(team) }
25 | }
26 |
27 | override fun getTeams(): List =
28 | teamRepository.findAll()
29 |
30 | override fun getTeam(teamName: String): Team =
31 | teamRepository.findByName(teamName)
32 | ?: throw MissingTeamException("Team does not exist.")
33 |
34 | }
35 |
--------------------------------------------------------------------------------
/src/main/kotlin/pl/braintelligence/projectmanager/core/team/domain/configuration/InMemoryTeamRepository.kt:
--------------------------------------------------------------------------------
1 | package pl.braintelligence.projectmanager.core.team.domain.configuration
2 |
3 | import pl.braintelligence.projectmanager.core.team.domain.Team
4 | import pl.braintelligence.projectmanager.core.team.ports.outgoing.TeamRepository
5 | import pl.braintelligence.projectmanager.shared.InMemoryCrudRepository
6 |
7 | class InMemoryTeamRepository : InMemoryCrudRepository(), TeamRepository {
8 |
9 | override fun existsByName(name: String): Boolean {
10 | return super.contains(id = name)
11 | }
12 |
13 | override fun findByName(name: String): Team? {
14 | return super.findById(id = name)
15 | }
16 |
17 | override fun save(team: Team) {
18 | super.save(entity = team, id = team.name)
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/src/main/kotlin/pl/braintelligence/projectmanager/core/team/domain/configuration/TeamConfiguration.kt:
--------------------------------------------------------------------------------
1 | package pl.braintelligence.projectmanager.core.team.domain.configuration
2 |
3 | import org.springframework.context.annotation.Bean
4 | import org.springframework.context.annotation.Configuration
5 | import pl.braintelligence.projectmanager.core.team.domain.TeamFacade
6 | import pl.braintelligence.projectmanager.core.team.ports.incoming.TeamManager
7 | import pl.braintelligence.projectmanager.core.team.ports.outgoing.TeamRepository
8 |
9 | @Configuration
10 | open class TeamConfiguration {
11 |
12 | open fun teamManager(): TeamManager =
13 | teamManager(InMemoryTeamRepository())
14 |
15 | @Bean
16 | open fun teamManager(teamRepository: TeamRepository): TeamManager =
17 | TeamFacade(teamRepository)
18 | }
19 |
--------------------------------------------------------------------------------
/src/main/kotlin/pl/braintelligence/projectmanager/core/team/ports/incoming/TeamManager.kt:
--------------------------------------------------------------------------------
1 | package pl.braintelligence.projectmanager.core.team.ports.incoming
2 |
3 | import pl.braintelligence.projectmanager.infrastructure.adapter.incoming.rest.dto.NewTeam
4 | import pl.braintelligence.projectmanager.infrastructure.adapter.incoming.rest.dto.TeamMember
5 | import pl.braintelligence.projectmanager.core.team.domain.Team
6 |
7 | /**
8 | * Primary Port
9 | */
10 |
11 | interface TeamManager {
12 |
13 | fun createTeam(newTeam: NewTeam)
14 |
15 | fun addMemberToTeam(teamName: String, teamMember: TeamMember)
16 |
17 | fun getTeams(): List
18 |
19 | fun getTeam(teamName: String): Team
20 |
21 | }
22 |
--------------------------------------------------------------------------------
/src/main/kotlin/pl/braintelligence/projectmanager/core/team/ports/outgoing/TeamRepository.kt:
--------------------------------------------------------------------------------
1 | package pl.braintelligence.projectmanager.core.team.ports.outgoing
2 |
3 | import pl.braintelligence.projectmanager.core.team.domain.Team
4 |
5 | /**
6 | * Secondary Port
7 | */
8 |
9 | interface TeamRepository {
10 |
11 | fun existsByName(name: String): Boolean
12 |
13 | fun findByName(name: String): Team?
14 |
15 | fun findAll(): List
16 |
17 | fun save(team: Team)
18 |
19 | }
20 |
--------------------------------------------------------------------------------
/src/main/kotlin/pl/braintelligence/projectmanager/infrastructure/adapter/incoming/rest/ProjectController.kt:
--------------------------------------------------------------------------------
1 | package pl.braintelligence.projectmanager.infrastructure.adapter.incoming.rest
2 |
3 | import org.springframework.beans.factory.annotation.Qualifier
4 | import org.springframework.http.HttpStatus
5 | import org.springframework.web.bind.annotation.*
6 | import pl.braintelligence.projectmanager.core.projects.domain.Project
7 | import pl.braintelligence.projectmanager.core.projects.ports.incoming.ProjectCreatorPort
8 | import pl.braintelligence.projectmanager.core.projects.ports.incoming.ProjectQueryPort
9 | import pl.braintelligence.projectmanager.infrastructure.adapter.incoming.rest.dto.ProjectDraft
10 | import pl.braintelligence.projectmanager.infrastructure.adapter.incoming.rest.dto.ProjectWithFeatures
11 |
12 | /**
13 | * Primary Adapter
14 | */
15 |
16 | @RestController
17 | @RequestMapping("/projects")
18 | internal class ProjectController(
19 | @Qualifier("projectCreatorService") private val projectCreatorPort: ProjectCreatorPort,
20 | @Qualifier("projectQueryService") private val projectQueryPort: ProjectQueryPort
21 | ) {
22 |
23 | @PostMapping("drafts")
24 | @ResponseStatus(HttpStatus.CREATED)
25 | fun createProjectDraft(
26 | @RequestBody projectDraft: ProjectDraft
27 | ): Project = projectCreatorPort.createProjectDraft(projectDraft)
28 |
29 | @PostMapping
30 | @ResponseStatus(HttpStatus.CREATED)
31 | fun createProjectWithFeatures(
32 | @RequestBody projectWithFeatures: ProjectWithFeatures
33 | ): Project = projectCreatorPort.createProjectWithFeatures(projectWithFeatures)
34 |
35 | @GetMapping("/{id}")
36 | @ResponseStatus(HttpStatus.OK)
37 | fun getProject(
38 | @PathVariable id: String
39 | ): Project = projectQueryPort.getProject(id)
40 |
41 | @GetMapping
42 | @ResponseStatus(HttpStatus.OK)
43 | fun getProjects(): List = TODO()
44 |
45 |
46 | }
47 |
--------------------------------------------------------------------------------
/src/main/kotlin/pl/braintelligence/projectmanager/infrastructure/adapter/incoming/rest/TeamController.kt:
--------------------------------------------------------------------------------
1 | package pl.braintelligence.projectmanager.infrastructure.adapter.incoming.rest
2 |
3 | import org.springframework.http.HttpStatus
4 | import org.springframework.web.bind.annotation.*
5 | import pl.braintelligence.projectmanager.core.team.ports.incoming.TeamManager
6 | import pl.braintelligence.projectmanager.infrastructure.adapter.incoming.rest.dto.ExistingTeam
7 | import pl.braintelligence.projectmanager.infrastructure.adapter.incoming.rest.dto.NewTeam
8 | import pl.braintelligence.projectmanager.infrastructure.adapter.incoming.rest.dto.TeamMember
9 |
10 | /**
11 | * Primary Adapter
12 | */
13 |
14 | @RestController
15 | @RequestMapping("/teams")
16 | class TeamController(
17 | private val teamManager: TeamManager
18 | ) {
19 |
20 | @ResponseStatus(HttpStatus.CREATED)
21 | @PostMapping
22 | fun createTeam(
23 | @RequestBody newTeamDto: NewTeam
24 | ) = teamManager.createTeam(newTeamDto)
25 |
26 | @ResponseStatus(HttpStatus.CREATED)
27 | @PostMapping("{teamName}/members")
28 | fun addMemberToTeam(
29 | @PathVariable teamName: String,
30 | @RequestBody teamMember: TeamMember
31 | ) = teamManager.addMemberToTeam(teamName, teamMember)
32 |
33 | @ResponseStatus(HttpStatus.OK)
34 | @GetMapping
35 | fun getTeams(): List =
36 | ExistingTeam.toExistingTeams(teamManager.getTeams())
37 |
38 |
39 | }
40 |
41 |
--------------------------------------------------------------------------------
/src/main/kotlin/pl/braintelligence/projectmanager/infrastructure/adapter/incoming/rest/dto/ProjectDtos.kt:
--------------------------------------------------------------------------------
1 | package pl.braintelligence.projectmanager.infrastructure.adapter.incoming.rest.dto
2 |
3 | import pl.braintelligence.projectmanager.core.projects.domain.Feature
4 |
5 | data class ProjectDraft(val projectName: String)
6 |
7 | data class ProjectWithFeatures(val projectName: String, val features: List)
8 |
--------------------------------------------------------------------------------
/src/main/kotlin/pl/braintelligence/projectmanager/infrastructure/adapter/incoming/rest/dto/TeamDtos.kt:
--------------------------------------------------------------------------------
1 | package pl.braintelligence.projectmanager.infrastructure.adapter.incoming.rest.dto
2 |
3 | import pl.braintelligence.projectmanager.core.team.domain.Employee
4 | import pl.braintelligence.projectmanager.core.team.domain.Team
5 |
6 | data class NewTeam(val name: String)
7 |
8 | data class TeamMember(
9 | val firstName: String,
10 | val lastName: String,
11 | val jobPosition: String
12 | ) {
13 | companion object {
14 | fun toTeamMembers(employees: List): List =
15 | employees.map {
16 | TeamMember(
17 | it.firstName,
18 | it.lastName,
19 | it.jobPosition.toString())
20 | }
21 | }
22 | }
23 |
24 | data class ExistingTeam(
25 | val name: String,
26 | val currentlyImplementedProjects: Int,
27 | val members: List,
28 | val isTeamBusy: Boolean
29 | ) {
30 | companion object {
31 | fun toExistingTeams(teams: List): List =
32 | teams.map {
33 | ExistingTeam(
34 | it.name,
35 | it.numberOfOngoingProjects,
36 | TeamMember.toTeamMembers(it.members),
37 | it.isTeamBusy())
38 | }
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/src/main/kotlin/pl/braintelligence/projectmanager/infrastructure/adapter/outgoing/mongo/project/TeamCreatorRepository.kt:
--------------------------------------------------------------------------------
1 | package pl.braintelligence.projectmanager.infrastructure.adapter.outgoing.mongo.project
2 |
3 | import org.springframework.data.repository.CrudRepository
4 | import org.springframework.stereotype.Component
5 | import org.springframework.stereotype.Repository
6 | import pl.braintelligence.projectmanager.core.projects.domain.Project
7 | import pl.braintelligence.projectmanager.core.projects.ports.outgoing.ProjectCreatorRepository
8 | import pl.braintelligence.projectmanager.infrastructure.adapter.outgoing.mongo.project.entities.DbProject
9 |
10 | @Repository
11 | interface MongoTeamCreationRepository : CrudRepository
12 |
13 | @Component
14 | class TeamCreatorRepository(
15 | private val mongo: MongoTeamCreationRepository
16 | ) : ProjectCreatorRepository {
17 |
18 | override fun save(project: Project) {
19 | val dbProject = DbProject.toDbProject(project)
20 | mongo.save(dbProject)
21 | }
22 |
23 | }
24 |
--------------------------------------------------------------------------------
/src/main/kotlin/pl/braintelligence/projectmanager/infrastructure/adapter/outgoing/mongo/project/TeamQueryRepository.kt:
--------------------------------------------------------------------------------
1 | package pl.braintelligence.projectmanager.infrastructure.adapter.outgoing.mongo.project
2 |
3 | import org.springframework.data.mongodb.repository.MongoRepository
4 | import org.springframework.stereotype.Component
5 | import org.springframework.stereotype.Repository
6 | import pl.braintelligence.projectmanager.core.projects.domain.Project
7 | import pl.braintelligence.projectmanager.core.projects.ports.outgoing.ProjectQueryRepository
8 | import pl.braintelligence.projectmanager.infrastructure.adapter.outgoing.mongo.project.entities.DbProject
9 |
10 | @Repository
11 | interface MongoTeamQueryRepository : MongoRepository
12 |
13 | @Component
14 | class TeamQueryRepository(
15 | private val mongo: MongoTeamCreationRepository
16 | ) : ProjectQueryRepository {
17 |
18 | override fun findById(id: String): Project? = DbProject.toProject(mongo.findById(id).get())
19 |
20 | }
21 |
--------------------------------------------------------------------------------
/src/main/kotlin/pl/braintelligence/projectmanager/infrastructure/adapter/outgoing/mongo/project/entities/DbFeature.kt:
--------------------------------------------------------------------------------
1 | package pl.braintelligence.projectmanager.infrastructure.adapter.outgoing.mongo.project.entities
2 |
3 | data class DbFeature(
4 | val name: String,
5 | val status: String,
6 | val priorityLevel: String
7 | )
8 |
--------------------------------------------------------------------------------
/src/main/kotlin/pl/braintelligence/projectmanager/infrastructure/adapter/outgoing/mongo/project/entities/DbProject.kt:
--------------------------------------------------------------------------------
1 | package pl.braintelligence.projectmanager.infrastructure.adapter.outgoing.mongo.project.entities
2 |
3 | import arrow.core.Try
4 | import arrow.core.getOrElse
5 | import org.springframework.data.annotation.Id
6 | import org.springframework.data.mongodb.core.mapping.Document
7 | import pl.braintelligence.projectmanager.core.projects.domain.Feature
8 | import pl.braintelligence.projectmanager.core.projects.domain.PriorityLevel
9 | import pl.braintelligence.projectmanager.core.projects.domain.Project
10 | import pl.braintelligence.projectmanager.core.projects.domain.Status
11 |
12 | @Document(collection = "projects")
13 | class DbProject(
14 | @Id val id: String,
15 | val name: String,
16 | val status: Status = Status.TO_DO,
17 | val teamAssigned: String = "",
18 | val features: List = listOf()
19 | ) {
20 | companion object {
21 | fun toDbProject(project: Project): DbProject = DbProject(
22 | project.id,
23 | project.name,
24 | project.status,
25 | project.teamAssigned,
26 | project.features.map {
27 | DbFeature(
28 | name = it.name,
29 | status = it.status.toString(),
30 | priorityLevel = it.priorityLevel.toString()
31 | )
32 | }
33 | )
34 |
35 | fun toProject(dbProject: DbProject): Project = Project(
36 | dbProject.id,
37 | dbProject.name,
38 | dbProject.status,
39 | dbProject.teamAssigned,
40 | dbProject.features.map {
41 | Feature(
42 | name = it.name,
43 | status = Try { Status.valueOf(it.status) }
44 | .getOrElse { throw IllegalArgumentException("Not valid Feature status") },
45 | priorityLevel = Try { PriorityLevel.valueOf(it.priorityLevel) }
46 | .getOrElse { throw IllegalArgumentException("Not valid Feature priorityLevel") }
47 | )
48 | }
49 |
50 | )
51 |
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/src/main/kotlin/pl/braintelligence/projectmanager/infrastructure/adapter/outgoing/mongo/team/MongoTeamRepository.kt:
--------------------------------------------------------------------------------
1 | package pl.braintelligence.projectmanager.infrastructure.adapter.outgoing.mongo.team
2 |
3 | import DbTeam
4 | import org.springframework.data.repository.CrudRepository
5 | import org.springframework.stereotype.Component
6 | import org.springframework.stereotype.Repository
7 | import pl.braintelligence.projectmanager.core.team.domain.Team
8 | import pl.braintelligence.projectmanager.core.team.ports.outgoing.TeamRepository
9 |
10 |
11 | @Repository
12 | interface CrudTeamRepository : CrudRepository {
13 | fun findByName(name: String): DbTeam?
14 | }
15 |
16 | /**
17 | * Secondary adapter
18 | */
19 |
20 | @Component
21 | class MongoTeamRepository(
22 | private val mongo: CrudTeamRepository
23 | ) : TeamRepository {
24 |
25 | override fun existsByName(name: String): Boolean =
26 | mongo.existsById(name)
27 |
28 | override fun findByName(name: String): Team? {
29 | val dbTeam = mongo.findByName(name)
30 | return dbTeam?.let { DbTeam.toTeam(it) }
31 | }
32 |
33 | override fun findAll(): List = DbTeam.toTeams(
34 | mongo.findAll().toList())
35 |
36 | override fun save(team: Team) {
37 | DbTeam.toDbTeam(team).also { mongo.save(it) }
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/src/main/kotlin/pl/braintelligence/projectmanager/infrastructure/adapter/outgoing/mongo/team/entities/DbEmployee.kt:
--------------------------------------------------------------------------------
1 | import pl.braintelligence.projectmanager.core.team.domain.Employee
2 | import pl.braintelligence.projectmanager.core.team.domain.JobPosition
3 |
4 | data class DbEmployee(
5 | private val firstName: String,
6 | private val lastName: String,
7 | private val jobPosition: String
8 | ) {
9 | companion object {
10 | fun toDbEmployee(employee: List): List =
11 | employee.map { toDbEmployee(it) }
12 |
13 | private fun toDbEmployee(employee: Employee): DbEmployee =
14 | DbEmployee(
15 | employee.firstName,
16 | employee.lastName,
17 | employee.jobPosition.toString()
18 | )
19 |
20 | fun toEmployee(dbEmployee: DbEmployee): Employee =
21 |
22 | Employee(
23 | dbEmployee.firstName,
24 | dbEmployee.lastName,
25 | JobPosition.valueOf(dbEmployee.jobPosition)
26 | )
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/src/main/kotlin/pl/braintelligence/projectmanager/infrastructure/adapter/outgoing/mongo/team/entities/DbTeam.kt:
--------------------------------------------------------------------------------
1 | import org.springframework.data.annotation.Id
2 | import org.springframework.data.mongodb.core.mapping.Document
3 | import pl.braintelligence.projectmanager.core.team.domain.Team
4 |
5 | @Document(collection = "teams")
6 | data class DbTeam(
7 | @Id private val name: String,
8 | private val numberOfOngoingProjects: Int,
9 | private val members: List
10 | ) {
11 | companion object {
12 | fun toDbTeam(team: Team): DbTeam =
13 | DbTeam(
14 | team.name,
15 | team.numberOfOngoingProjects,
16 | DbEmployee.toDbEmployee(team.members)
17 | )
18 |
19 | fun toTeams(dbTeams: List): List =
20 | dbTeams.map {
21 | Team(
22 | it.name,
23 | it.numberOfOngoingProjects,
24 | it.members.map { member -> DbEmployee.toEmployee(member) }
25 | )
26 | }
27 |
28 | fun toTeam(dbTeam: DbTeam): Team? =
29 | Team(
30 | dbTeam.name,
31 | dbTeam.numberOfOngoingProjects,
32 | dbTeam.members.map { DbEmployee.toEmployee(it) }
33 | )
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/src/main/kotlin/pl/braintelligence/projectmanager/infrastructure/error/ErrorHandler.kt:
--------------------------------------------------------------------------------
1 | package pl.braintelligence.projectmanager.infrastructure.error
2 |
3 | import org.slf4j.LoggerFactory
4 | import org.springframework.http.HttpStatus
5 | import org.springframework.http.ResponseEntity
6 | import org.springframework.http.ResponseEntity.status
7 | import org.springframework.web.bind.annotation.ExceptionHandler
8 | import org.springframework.web.bind.annotation.RestControllerAdvice
9 | import pl.braintelligence.projectmanager.core.team.domain.EntityAlreadyExistsException
10 | import pl.braintelligence.projectmanager.shared.DomainException
11 | import java.lang.invoke.MethodHandles
12 | import java.time.Instant
13 | import javax.servlet.http.HttpServletRequest
14 |
15 |
16 | @RestControllerAdvice
17 | class ErrorHandler {
18 |
19 | @ExceptionHandler(EntityAlreadyExistsException::class)
20 | fun handleEntityAlreadyExistsException(exception: DomainException, request: HttpServletRequest)
21 | : ResponseEntity {
22 | return mapToResponse(exception, request, HttpStatus.UNPROCESSABLE_ENTITY)
23 | }
24 |
25 | private fun mapToResponse(ex: DomainException, request: HttpServletRequest, httpStatus: HttpStatus): ResponseEntity {
26 | val errorMessage = ex.message?.let { ErrorMessage(it, Instant.now()) }
27 | log.error(createLog(request, httpStatus, ex.message))
28 | return status(httpStatus)
29 | .body(errorMessage)
30 | }
31 |
32 | private fun createLog(request: HttpServletRequest, status: HttpStatus, message: String?): String {
33 | return "${request.method} on \"${request.requestURI}\" with status \"${status.value()}\" and message = \"$message\" "
34 | }
35 |
36 | companion object {
37 | private val log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass())
38 | }
39 | }
40 |
41 | data class ErrorMessage(val message: String, val timestamp: Instant)
42 |
43 |
44 |
--------------------------------------------------------------------------------
/src/main/kotlin/pl/braintelligence/projectmanager/shared/DomainException.kt:
--------------------------------------------------------------------------------
1 | package pl.braintelligence.projectmanager.shared
2 |
3 | open class DomainException(message: String) : Exception(message)
4 |
--------------------------------------------------------------------------------
/src/main/kotlin/pl/braintelligence/projectmanager/shared/InMemoryRepository.kt:
--------------------------------------------------------------------------------
1 | package pl.braintelligence.projectmanager.shared
2 |
3 | import java.util.concurrent.ConcurrentHashMap
4 |
5 | abstract class InMemoryCrudRepository : CustomRepository {
6 |
7 | private val dataStore = ConcurrentHashMap()
8 |
9 | override fun save(entity: ENTITY, id: ID) = run { dataStore[id] = entity }
10 |
11 | override fun contains(id: ID): Boolean = dataStore.containsKey(id)
12 |
13 | override fun findById(id: ID): ENTITY? = dataStore[id]
14 |
15 | override fun findAll(): List = dataStore.values.toList()
16 |
17 | }
18 |
19 | interface CustomRepository {
20 | fun save(entity: ENTITY, id: ID)
21 | fun contains(id: ID): Boolean
22 | fun findById(id: ID): ENTITY?
23 | fun findAll(): List
24 | }
25 |
--------------------------------------------------------------------------------
/src/main/resources/application.yml:
--------------------------------------------------------------------------------
1 | client:
2 | connectTimeout: 1500
3 | readTimeout: 3000
4 |
--------------------------------------------------------------------------------
/src/test/groovy/pl/braintelligence/projectmanager/base/BaseDtoObjects.groovy:
--------------------------------------------------------------------------------
1 | package pl.braintelligence.projectmanager.base
2 |
3 | import pl.braintelligence.projectmanager.core.projects.domain.Feature
4 | import pl.braintelligence.projectmanager.core.projects.domain.PriorityLevel
5 | import pl.braintelligence.projectmanager.core.projects.domain.Status
6 | import pl.braintelligence.projectmanager.infrastructure.adapter.incoming.rest.dto.NewTeam
7 | import pl.braintelligence.projectmanager.infrastructure.adapter.incoming.rest.dto.ProjectDraft
8 | import pl.braintelligence.projectmanager.infrastructure.adapter.incoming.rest.dto.ProjectWithFeatures
9 | import pl.braintelligence.projectmanager.infrastructure.adapter.incoming.rest.dto.TeamMember
10 |
11 | trait BaseDtoObjects {
12 |
13 | // projects
14 | ProjectDraft newProjectDraftDto = new ProjectDraft("qwerty")
15 |
16 | Feature feature = new Feature("feature name 1", Status.TO_DO, PriorityLevel.NOT_DEFINED)
17 |
18 | ProjectWithFeatures newProjectWithFeaturesDto = new ProjectWithFeatures("feature 1", List.of(feature))
19 |
20 | ProjectDraft newProjectDraft = new ProjectDraft("project name")
21 |
22 | // teams
23 | NewTeam newTeamDto = new NewTeam("123")
24 | NewTeam newTeamDto1 = new NewTeam("123456")
25 |
26 | TeamMember teamMemberDto = new TeamMember(
27 | "firstName",
28 | "LastName",
29 | "DEVELOPER"
30 | )
31 |
32 | }
33 |
--------------------------------------------------------------------------------
/src/test/groovy/pl/braintelligence/projectmanager/base/BaseIntegrationTest.groovy:
--------------------------------------------------------------------------------
1 | package pl.braintelligence.projectmanager.base
2 |
3 | import com.github.tomakehurst.wiremock.junit.WireMockRule
4 | import org.junit.Rule
5 | import org.springframework.beans.factory.annotation.Autowired
6 | import org.springframework.boot.test.context.SpringBootTest
7 | import org.springframework.boot.test.web.client.TestRestTemplate
8 | import org.springframework.data.mongodb.core.MongoTemplate
9 | import org.springframework.http.ResponseEntity
10 | import pl.braintelligence.projectmanager.base.http.BaseHttpMethods
11 | import pl.braintelligence.projectmanager.infrastructure.adapter.incoming.rest.dto.NewTeam
12 | import spock.lang.Specification
13 |
14 | import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.RANDOM_PORT
15 |
16 | @SpringBootTest(webEnvironment = RANDOM_PORT)
17 | class BaseIntegrationTest extends Specification implements BaseHttpMethods, BaseDtoObjects {
18 |
19 | @Rule
20 | public WireMockRule reportingService = new WireMockRule(8081)
21 |
22 | @Autowired
23 | private TestRestTemplate restTemplate
24 |
25 | @Autowired
26 | private MongoTemplate mongoTemplate
27 |
28 | void setup() {
29 | clearMongoDB()
30 | }
31 |
32 | private void clearMongoDB() {
33 | for (def collection : mongoTemplate.collectionNames) {
34 | mongoTemplate.dropCollection(collection)
35 | }
36 | }
37 |
38 | protected ResponseEntity prepareNewTeam(String teamName) {
39 | def newTeam = new NewTeam(teamName)
40 | post("/teams", newTeam)
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/src/test/groovy/pl/braintelligence/projectmanager/base/BaseUnitTest.groovy:
--------------------------------------------------------------------------------
1 | package pl.braintelligence.projectmanager.base
2 |
3 |
4 | import spock.lang.Specification
5 |
6 | class BaseUnitTest extends Specification implements BaseDtoObjects {
7 |
8 | }
9 |
--------------------------------------------------------------------------------
/src/test/groovy/pl/braintelligence/projectmanager/base/http/BaseHttpMethods.groovy:
--------------------------------------------------------------------------------
1 | package pl.braintelligence.projectmanager.base.http
2 |
3 | import org.springframework.beans.factory.annotation.Autowired
4 | import org.springframework.boot.test.web.client.TestRestTemplate
5 | import org.springframework.core.ParameterizedTypeReference
6 | import org.springframework.http.HttpEntity
7 | import org.springframework.http.HttpHeaders
8 | import org.springframework.http.HttpMethod
9 | import org.springframework.http.ResponseEntity
10 |
11 |
12 | trait BaseHttpMethods {
13 |
14 | @Autowired
15 | TestRestTemplate restTemplate
16 |
17 | def ResponseEntity get(String uri, Class responseBodyType) {
18 | return sendRequest(uri, HttpMethod.GET, null, responseBodyType)
19 | }
20 |
21 | def ResponseEntity get(String uri, ParameterizedTypeReference responseBodyType) {
22 | return sendRequest(uri, HttpMethod.GET, null, responseBodyType)
23 | }
24 |
25 | ResponseEntity post(String uri, Object requestBody) {
26 | return sendRequest(uri, HttpMethod.POST, requestBody, Object)
27 | }
28 |
29 | def put(String uri, Object requestBody) {
30 | return sendRequest(uri, HttpMethod.PUT, requestBody, Object)
31 | }
32 |
33 | ResponseEntity patch(String uri) {
34 | return sendRequest(uri, HttpMethod.PATCH, null, Object)
35 | }
36 |
37 | ResponseEntity patch(String uri, Object requestBody) {
38 | return sendRequest(uri, HttpMethod.PATCH, requestBody, Object)
39 | }
40 |
41 | def HttpEntity preparePayload(T data, Map> additionalHeaders = [:]) {
42 | def headers = new HttpHeaders()
43 |
44 | headers.putAll(additionalHeaders)
45 | return new HttpEntity(data, headers)
46 | }
47 |
48 | private ResponseEntity sendRequest(String uri, HttpMethod method, Object requestBody, Class responseBodyType) {
49 | def entity = new HttpEntity<>(requestBody)
50 | return restTemplate.exchange(uri, method, entity, responseBodyType)
51 | }
52 |
53 | private ResponseEntity sendRequest(String uri, HttpMethod method, Object requestBody, ParameterizedTypeReference responseBodyType) {
54 | def entity = new HttpEntity<>(requestBody)
55 | return restTemplate.exchange(uri, method, entity, responseBodyType)
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/src/test/groovy/pl/braintelligence/projectmanager/project/ProjectAcceptanceTest.groovy:
--------------------------------------------------------------------------------
1 | package pl.braintelligence.projectmanager.project
2 |
3 |
4 | import org.springframework.http.HttpStatus
5 | import pl.braintelligence.projectmanager.base.BaseIntegrationTest
6 | import pl.braintelligence.projectmanager.core.projects.domain.Project
7 |
8 | class ProjectAcceptanceTest extends BaseIntegrationTest {
9 |
10 | def "Should create project draft"() {
11 | given: "create new project draft"
12 | def response = post("/projects/drafts", newProjectDraft)
13 | response.statusCode == HttpStatus.CREATED
14 |
15 | when: "browse for this project draft"
16 | response = get("/projects/${response.body.id}", Project.class)
17 |
18 | then:
19 | response.statusCode == HttpStatus.OK
20 |
21 | when: "create new project with features"
22 | response = post("/projects", newProjectWithFeaturesDto)
23 |
24 | then:
25 | response.statusCode == HttpStatus.CREATED
26 |
27 | // when: "browse for all projects"
28 | // response = get("/projects", new ParameterizedTypeReference>() {})
29 |
30 | // then:
31 | // response.statusCode == HttpStatus.OK
32 |
33 | }
34 | }
35 |
36 |
--------------------------------------------------------------------------------
/src/test/groovy/pl/braintelligence/projectmanager/project/base/BaseProjectUnitTest.groovy:
--------------------------------------------------------------------------------
1 | package pl.braintelligence.projectmanager.project.base
2 |
3 | import pl.braintelligence.projectmanager.base.BaseUnitTest
4 | import pl.braintelligence.projectmanager.core.projects.domain.Project
5 | import pl.braintelligence.projectmanager.core.projects.domain.ProjectFactory
6 | import pl.braintelligence.projectmanager.core.projects.domain.Status
7 | import pl.braintelligence.projectmanager.core.projects.domain.configuration.InMemoryProjectRepository
8 | import pl.braintelligence.projectmanager.core.projects.domain.configuration.ProjectConfiguration
9 | import pl.braintelligence.projectmanager.core.projects.ports.incoming.ProjectCreatorPort
10 | import pl.braintelligence.projectmanager.core.projects.ports.incoming.ProjectQueryPort
11 |
12 | class BaseProjectUnitTest extends BaseUnitTest {
13 |
14 | protected InMemoryProjectRepository inMemoryProjectRepository = new InMemoryProjectRepository()
15 |
16 | protected ProjectCreatorPort projectCreator =
17 | new ProjectConfiguration()
18 | .buildProjectCreator(Mock(ProjectFactory), inMemoryProjectRepository)
19 |
20 | protected ProjectQueryPort projectQuery =
21 | new ProjectConfiguration()
22 | .buildProjectQuery(inMemoryProjectRepository)
23 |
24 | protected void verifyProjectDraft(Project project) {
25 | assert with(project) {
26 | id != null
27 | name == newProjectDraftDto.projectName
28 | status == Status.TO_DO
29 | teamAssigned == ""
30 | features == []
31 | }
32 | }
33 |
34 | protected void verifyProjectWithFeatures(Project project) {
35 | assert with(project) {
36 | id != null
37 | name == newProjectWithFeaturesDto.projectName
38 | status == Status.TO_DO
39 | teamAssigned.isBlank()
40 | with(features[0]) {
41 | name == newProjectWithFeaturesDto.features[0].name
42 | status == newProjectWithFeaturesDto.features[0].status
43 | priorityLevel == newProjectWithFeaturesDto.features[0].priorityLevel
44 | }
45 | }
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/src/test/groovy/pl/braintelligence/projectmanager/project/domain/ProjectCreationTest.groovy:
--------------------------------------------------------------------------------
1 | package pl.braintelligence.projectmanager.project.domain
2 |
3 |
4 | import pl.braintelligence.projectmanager.project.base.BaseProjectUnitTest
5 |
6 | class ProjectCreationTest extends BaseProjectUnitTest {
7 |
8 | def "Should create a project draft and browse for it"() {
9 | when:
10 | def project = projectCreator.createProjectDraft(newProjectDraftDto)
11 |
12 | then:
13 | verifyProjectDraft(project)
14 | }
15 |
16 | def "Should create project with features and browse it"() {
17 | when:
18 | def project = projectCreator.createProjectWithFeatures(newProjectWithFeaturesDto)
19 |
20 | then:
21 | verifyProjectWithFeatures(project)
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/src/test/groovy/pl/braintelligence/projectmanager/project/domain/ProjectQueryTest.groovy:
--------------------------------------------------------------------------------
1 | package pl.braintelligence.projectmanager.project.domain
2 |
3 | import pl.braintelligence.projectmanager.project.base.BaseProjectUnitTest
4 |
5 | class ProjectQueryTest extends BaseProjectUnitTest {
6 |
7 | def "Should browse for project"() {
8 | given:
9 | def project = projectCreator.createProjectWithFeatures(newProjectWithFeaturesDto)
10 |
11 | when:
12 | def response = projectQuery.getProject(project.id)
13 |
14 | then:
15 | verifyProjectWithFeatures(response)
16 | }
17 |
18 | }
19 |
--------------------------------------------------------------------------------
/src/test/groovy/pl/braintelligence/projectmanager/team/TeamAcceptanceTest.groovy:
--------------------------------------------------------------------------------
1 | package pl.braintelligence.projectmanager.team
2 |
3 | import org.springframework.core.ParameterizedTypeReference
4 | import org.springframework.http.HttpStatus
5 | import pl.braintelligence.projectmanager.base.BaseIntegrationTest
6 | import pl.braintelligence.projectmanager.core.team.domain.Team
7 |
8 | class TeamAcceptanceTest extends BaseIntegrationTest {
9 |
10 | def "User flow while using project manager"() {
11 | when: "new team is created"
12 | prepareNewTeam(newTeamDto.name)
13 |
14 | then: "user gets all teams created"
15 | get('/teams', new ParameterizedTypeReference>() {})
16 | .statusCode == HttpStatus.OK
17 |
18 | when: "new member is added to a team"
19 | post('/teams/teamName/members', teamMemberDto)
20 | .statusCode == HttpStatus.CREATED
21 |
22 | then: "browse for new member"
23 | get('/teams', new ParameterizedTypeReference>() {})
24 | .statusCode == HttpStatus.OK
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/src/test/groovy/pl/braintelligence/projectmanager/team/base/BaseTeamUnitTest.groovy:
--------------------------------------------------------------------------------
1 | package pl.braintelligence.projectmanager.team.base
2 |
3 | import pl.braintelligence.projectmanager.base.BaseUnitTest
4 | import pl.braintelligence.projectmanager.core.team.domain.configuration.TeamConfiguration
5 | import pl.braintelligence.projectmanager.core.team.ports.incoming.TeamManager
6 |
7 | class BaseTeamUnitTest extends BaseUnitTest {
8 |
9 | protected TeamManager teamService = new TeamConfiguration().teamManager()
10 |
11 | }
12 |
--------------------------------------------------------------------------------
/src/test/groovy/pl/braintelligence/projectmanager/team/domain/AddingTeamMembersToTeamTest.groovy:
--------------------------------------------------------------------------------
1 | package pl.braintelligence.projectmanager.team.domain
2 |
3 |
4 | import pl.braintelligence.projectmanager.team.base.BaseTeamUnitTest
5 |
6 | class AddingTeamMembersToTeamTest extends BaseTeamUnitTest {
7 |
8 | def "Should add member to a team"() {
9 | given: "new team is created"
10 | teamService.createTeam(newTeamDto)
11 |
12 | and: "member is added to a team"
13 | teamService.addMemberToTeam(newTeamDto.name, teamMemberDto)
14 |
15 | when: "teams are retrieved"
16 | def response = teamService.getTeam(newTeamDto.name)
17 |
18 | then: "new member is in a team"
19 | with(response.members[0]) {
20 | firstName == teamMemberDto.firstName
21 | lastName == teamMemberDto.lastName
22 | jobPosition.name() == teamMemberDto.jobPosition
23 | }
24 | }
25 |
26 | }
27 |
--------------------------------------------------------------------------------
/src/test/groovy/pl/braintelligence/projectmanager/team/domain/TeamCreationTest.groovy:
--------------------------------------------------------------------------------
1 | package pl.braintelligence.projectmanager.team.domain
2 |
3 | import pl.braintelligence.projectmanager.team.base.BaseTeamUnitTest
4 |
5 | class TeamCreationTest extends BaseTeamUnitTest {
6 |
7 | def "Should create a team (with default values)"() {
8 | given:
9 | teamService.createTeam(newTeamDto)
10 |
11 | when:
12 | def response = teamService.getTeam(newTeamDto.name)
13 |
14 | then:
15 | with(response) {
16 | name == newTeamDto.name
17 | numberOfOngoingProjects == 0
18 | members == []
19 | }
20 | }
21 |
22 | def "Should create a teams (with default values)"() {
23 | given:
24 | teamService.createTeam(newTeamDto)
25 | teamService.createTeam(newTeamDto1)
26 |
27 | when:
28 | def response = teamService.getTeams()
29 |
30 | then:
31 | with(response) {
32 | name[0] == newTeamDto.name
33 | name[1] == newTeamDto1.name
34 | }
35 | }
36 |
37 | }
38 |
--------------------------------------------------------------------------------
/src/test/groovy/pl/braintelligence/projectmanager/team/domain/TeamMembersValidationTest.groovy:
--------------------------------------------------------------------------------
1 | package pl.braintelligence.projectmanager.team.domain
2 |
3 |
4 | import pl.braintelligence.projectmanager.core.team.domain.InvalidTeamMemberException
5 | import pl.braintelligence.projectmanager.infrastructure.adapter.incoming.rest.dto.TeamMember
6 | import pl.braintelligence.projectmanager.team.base.BaseTeamUnitTest
7 | import spock.lang.Unroll
8 |
9 | class TeamMembersValidationTest extends BaseTeamUnitTest {
10 |
11 | def "Should add member to a team"() {
12 | given: "team is created"
13 | def teamName = newTeamDto.name
14 | teamService.createTeam(newTeamDto)
15 |
16 | when: "two members are added to a team"
17 | teamService.addMemberToTeam(teamName, teamMemberDto)
18 | teamService.addMemberToTeam(teamName, teamMemberDto)
19 |
20 | then: "two members are in a team"
21 | teamService.getTeam(teamName).members.size() == 2
22 | }
23 |
24 | @Unroll
25 | def "Should throw IllegalArgumentException when #errorMessage"() {
26 | given:
27 | def teamMember = new TeamMember(firstName, lastName, jobPosition)
28 | teamService.createTeam(newTeamDto)
29 |
30 | when:
31 | teamService.addMemberToTeam(newTeamDto.name, teamMember)
32 |
33 | then:
34 | def thrown = thrown(InvalidTeamMemberException.class)
35 | thrown.message == errorMessage
36 |
37 | where:
38 | errorMessage | firstName | lastName | jobPosition
39 | "Empty member first name." | ' ' | 'valid last name' | 'DEVELOPER'
40 | "Empty member last name." | 'valid first name' | ' ' | 'DEVELOPER'
41 | "Invalid job position." | 'valid first name' | 'valid last name' | 'upps! not valid'
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/src/test/groovy/pl/braintelligence/projectmanager/team/domain/TeamValidationTest.groovy:
--------------------------------------------------------------------------------
1 | package pl.braintelligence.projectmanager.team.domain
2 |
3 |
4 | import pl.braintelligence.projectmanager.core.team.domain.EntityAlreadyExistsException
5 | import pl.braintelligence.projectmanager.core.team.domain.InvalidTeamException
6 | import pl.braintelligence.projectmanager.core.team.domain.MissingTeamException
7 | import pl.braintelligence.projectmanager.core.team.domain.Team
8 | import pl.braintelligence.projectmanager.team.base.BaseTeamUnitTest
9 |
10 | class TeamValidationTest extends BaseTeamUnitTest {
11 |
12 | def "Should throw exception when team does not exist"() {
13 | when:
14 | teamService.addMemberToTeam("non-existent team name", teamMemberDto)
15 |
16 | then:
17 | def thrown = thrown(MissingTeamException.class)
18 | thrown.message == "Team does not exist."
19 | }
20 |
21 | def "Should throw an exception when team name is empty"() {
22 | when:
23 | new Team(teamName)
24 |
25 | then:
26 | def ex = thrown(InvalidTeamException.class)
27 | ex.message == "Empty team name."
28 |
29 | where:
30 | teamName << ['', ' ']
31 | }
32 |
33 | def "Should not create team that already exists"() {
34 | given: "create a team"
35 | teamService.createTeam(newTeamDto)
36 |
37 | when: "create another team with the same name"
38 | teamService.createTeam(newTeamDto)
39 |
40 | then:
41 | def thrown = thrown(EntityAlreadyExistsException.class)
42 | thrown.message == "Team already exist."
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/src/test/kotlin/pl/braintelligence/projectmanager/core/team/HexagonalArchitectureTest.kt:
--------------------------------------------------------------------------------
1 | package pl.braintelligence.projectmanager.core.team
2 |
3 | import com.tngtech.archunit.junit.AnalyzeClasses
4 | import com.tngtech.archunit.junit.ArchTest
5 | import com.tngtech.archunit.lang.syntax.ArchRuleDefinition.classes
6 | import com.tngtech.archunit.lang.syntax.ArchRuleDefinition.noClasses
7 | import com.tngtech.archunit.library.dependencies.SlicesRuleDefinition
8 | import pl.braintelligence.projectmanager.Application
9 |
10 | /**
11 | * Feel free to create PR with more rules :)
12 | */
13 |
14 | //@TestInstance(TestInstance.Lifecycle.PER_CLASS)
15 | @AnalyzeClasses(packagesOf = [Application::class])
16 | internal class HexagonalArchitectureTest {
17 |
18 | @ArchTest
19 | val `other domain features does dont depend on each other` =
20 | SlicesRuleDefinition.slices()
21 | .matching("..core.(*)..")
22 | .should()
23 | .notDependOnEachOther()
24 |
25 | @ArchTest
26 | val `Core (domain) does not have infrastructure code` =
27 | noClasses()
28 | .that()
29 | .resideInAPackage("..core..")
30 | .should()
31 | .accessClassesThat()
32 | .resideInAnyPackage("..infrastructure..")
33 |
34 | @ArchTest
35 | val `Controllers are in adapters` =
36 | classes()
37 | .that()
38 | .haveSimpleNameStartingWith("Controller")
39 | .should()
40 | .resideInAPackage("..adapter..")
41 |
42 | }
43 |
--------------------------------------------------------------------------------
/src/test/kotlin/pl/braintelligence/projectmanager/core/team/NoSpringInDomainTest.kt:
--------------------------------------------------------------------------------
1 | package pl.braintelligence.projectmanager.core.team
2 |
3 | import com.tngtech.archunit.junit.AnalyzeClasses
4 | import com.tngtech.archunit.junit.ArchTest
5 | import com.tngtech.archunit.lang.syntax.ArchRuleDefinition.noClasses
6 | import org.junit.jupiter.api.TestInstance
7 |
8 | /**
9 | * Feel free to create PR with more rules :)
10 | */
11 |
12 | @TestInstance(TestInstance.Lifecycle.PER_CLASS)
13 | @AnalyzeClasses(packagesOf = [NoSpringInDomainTest::class])
14 | internal class NoSpringInDomainTest {
15 |
16 | @ArchTest
17 | val `Core (domain) should not depend on spring` =
18 | noClasses()
19 | .that()
20 | .resideInAPackage("..pl.braintelligence.projectmanager.core..domain..")
21 | .should()
22 | .dependOnClassesThat()
23 | .resideInAPackage("org.springframework..")
24 | }
25 |
--------------------------------------------------------------------------------