Compare commits
6 Commits
master
...
f98e1227e8
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f98e1227e8 | ||
|
|
ed0acfc5dc | ||
|
|
ee26a56627 | ||
|
|
2a5bbe5b57 | ||
|
|
8fa13103f3 | ||
|
|
2ca85aab88 |
11
bruno-rest/GiteaActionsWorkshop/Get all products.bru
Normal file
11
bruno-rest/GiteaActionsWorkshop/Get all products.bru
Normal file
@@ -0,0 +1,11 @@
|
||||
meta {
|
||||
name: Get all products
|
||||
type: http
|
||||
seq: 2
|
||||
}
|
||||
|
||||
get {
|
||||
url: {{url}}/api/products
|
||||
body: none
|
||||
auth: none
|
||||
}
|
||||
9
bruno-rest/GiteaActionsWorkshop/bruno.json
Normal file
9
bruno-rest/GiteaActionsWorkshop/bruno.json
Normal file
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"version": "1",
|
||||
"name": "GiteaActionsWorkshop",
|
||||
"type": "collection",
|
||||
"ignore": [
|
||||
"node_modules",
|
||||
".git"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
vars {
|
||||
url: http://localhost:8080
|
||||
}
|
||||
@@ -1,7 +1,6 @@
|
||||
plugins {
|
||||
kotlin("jvm") version "1.9.25"
|
||||
kotlin("plugin.spring") version "1.9.25"
|
||||
id("org.springframework.boot") version "3.4.4"
|
||||
kotlin("jvm") version "2.1.20"
|
||||
kotlin("plugin.spring") version "2.1.20"
|
||||
id("io.spring.dependency-management") version "1.1.7"
|
||||
}
|
||||
|
||||
@@ -18,13 +17,24 @@ repositories {
|
||||
mavenCentral()
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation("org.springframework.boot:spring-boot-starter-web")
|
||||
implementation("com.fasterxml.jackson.module:jackson-module-kotlin")
|
||||
implementation("org.jetbrains.kotlin:kotlin-reflect")
|
||||
testImplementation("org.springframework.boot:spring-boot-starter-test")
|
||||
testImplementation("org.jetbrains.kotlin:kotlin-test-junit5")
|
||||
testRuntimeOnly("org.junit.platform:junit-platform-launcher")
|
||||
subprojects {
|
||||
apply(plugin = "java")
|
||||
apply(plugin = "org.jetbrains.kotlin.jvm")
|
||||
|
||||
repositories {
|
||||
mavenCentral()
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation(platform("org.springframework.boot:spring-boot-dependencies:3.4.4"))
|
||||
implementation("com.michael-bull.kotlin-result:kotlin-result:2.0.1")
|
||||
}
|
||||
}
|
||||
|
||||
dependencyManagement {
|
||||
imports {
|
||||
mavenBom("org.springframework.boot:spring-boot-dependencies:3.4.4")
|
||||
}
|
||||
}
|
||||
|
||||
kotlin {
|
||||
|
||||
12
demo-application/build.gradle.kts
Normal file
12
demo-application/build.gradle.kts
Normal file
@@ -0,0 +1,12 @@
|
||||
plugins {
|
||||
kotlin("jvm")
|
||||
id("io.spring.dependency-management") version "1.1.7"
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation(kotlin("stdlib"))
|
||||
implementation(project(":demo-domain"))
|
||||
implementation("org.springframework:spring-context")
|
||||
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.4")
|
||||
implementation("com.michael-bull.kotlin-result:kotlin-result-coroutines:2.0.1")
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
package com.example.demo.application.product
|
||||
|
||||
import com.example.demo.domain.core.error.FunctionalError
|
||||
import com.example.demo.domain.product.inputport.ProductInputPort
|
||||
import com.example.demo.domain.product.model.Product
|
||||
import com.example.demo.domain.product.model.ProductType
|
||||
import com.example.demo.domain.product.outputport.ProductOutputPort
|
||||
import com.github.michaelbull.result.*
|
||||
import com.github.michaelbull.result.coroutines.coroutineBinding
|
||||
import org.springframework.stereotype.Service
|
||||
import java.util.*
|
||||
|
||||
@Service
|
||||
class ProductUseCases(
|
||||
private val productOutputPort: ProductOutputPort
|
||||
) : ProductInputPort {
|
||||
override suspend fun getAll(): Result<List<Product>, FunctionalError> = productOutputPort.getAll()
|
||||
.mapError { FunctionalError(it.message) }
|
||||
|
||||
// override suspend fun create(name: String, type: ProductType): Result<Product, FunctionalError> {
|
||||
// if (name.isBlank()) {
|
||||
// return Err(FunctionalError("Product name should be set."))
|
||||
// }
|
||||
//
|
||||
// val newProduct = Product(id = UUID.randomUUID(), name, type)
|
||||
// return productOutputPort.save(newProduct)
|
||||
// .mapError { FunctionalError(it.message) }
|
||||
// .map { newProduct }
|
||||
// }
|
||||
|
||||
override suspend fun create(name: String, type: ProductType): Result<Product, FunctionalError> = coroutineBinding {
|
||||
validateName(name).bind()
|
||||
|
||||
val newProduct = Product(id = UUID.randomUUID(), name, type)
|
||||
productOutputPort.save(newProduct)
|
||||
.mapError { FunctionalError(it.message) }
|
||||
.map { newProduct }
|
||||
.bind()
|
||||
}
|
||||
|
||||
private fun validateName(name: String): Result<Unit, FunctionalError> = when {
|
||||
name.isBlank() -> Err(FunctionalError("Product name should be set."))
|
||||
else -> Ok(Unit)
|
||||
}
|
||||
}
|
||||
7
demo-domain/build.gradle.kts
Normal file
7
demo-domain/build.gradle.kts
Normal file
@@ -0,0 +1,7 @@
|
||||
plugins {
|
||||
kotlin("jvm")
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation(kotlin("stdlib"))
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
package com.example.demo.domain.core.error
|
||||
|
||||
open class DomainError(val message: String)
|
||||
@@ -0,0 +1,3 @@
|
||||
package com.example.demo.domain.core.error
|
||||
|
||||
class FunctionalError(message: String) : DomainError(message)
|
||||
@@ -0,0 +1,3 @@
|
||||
package com.example.demo.domain.core.error
|
||||
|
||||
class TechnicalError(message: String) : DomainError(message)
|
||||
@@ -0,0 +1,11 @@
|
||||
package com.example.demo.domain.product.inputport
|
||||
|
||||
import com.example.demo.domain.core.error.FunctionalError
|
||||
import com.example.demo.domain.product.model.Product
|
||||
import com.example.demo.domain.product.model.ProductType
|
||||
import com.github.michaelbull.result.Result
|
||||
|
||||
interface ProductInputPort {
|
||||
suspend fun create(name: String, type: ProductType): Result<Product, FunctionalError>
|
||||
suspend fun getAll(): Result<List<Product>, FunctionalError>
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
package com.example.demo.domain.product.model
|
||||
|
||||
import java.util.UUID
|
||||
|
||||
data class Product(
|
||||
val id: UUID,
|
||||
val name: String,
|
||||
val type: ProductType
|
||||
)
|
||||
@@ -0,0 +1,7 @@
|
||||
package com.example.demo.domain.product.model
|
||||
|
||||
enum class ProductType {
|
||||
SOLID,
|
||||
LIQUID,
|
||||
GAS
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
package com.example.demo.domain.product.outputport
|
||||
|
||||
import com.example.demo.domain.core.error.TechnicalError
|
||||
import com.example.demo.domain.product.model.Product
|
||||
import com.github.michaelbull.result.Result
|
||||
import java.util.UUID
|
||||
|
||||
interface ProductOutputPort {
|
||||
fun getById(id: UUID): Product?
|
||||
|
||||
suspend fun getAll(): Result<List<Product>, TechnicalError>
|
||||
|
||||
fun save(product: Product): Result<Unit, TechnicalError>
|
||||
|
||||
fun deleteById(id: UUID)
|
||||
}
|
||||
15
demo-exposition/build.gradle.kts
Normal file
15
demo-exposition/build.gradle.kts
Normal file
@@ -0,0 +1,15 @@
|
||||
plugins {
|
||||
kotlin("jvm")
|
||||
id("io.spring.dependency-management") version "1.1.7"
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation(kotlin("stdlib"))
|
||||
implementation(project(":demo-application"))
|
||||
implementation(project(":demo-domain"))
|
||||
implementation("org.springframework:spring-context")
|
||||
implementation("org.springframework:spring-web")
|
||||
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.10.2")
|
||||
implementation("com.michael-bull.kotlin-result:kotlin-result-coroutines:2.0.1")
|
||||
implementation("com.fasterxml.jackson.module:jackson-module-kotlin:2.18.3")
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
package com.example.demo.exposition.product
|
||||
|
||||
import com.example.demo.domain.core.error.FunctionalError
|
||||
import com.example.demo.domain.product.inputport.ProductInputPort
|
||||
import com.example.demo.domain.product.model.Product
|
||||
import com.example.demo.exposition.product.model.ProductCreationRequest
|
||||
import com.example.demo.exposition.product.model.ProductDto
|
||||
import com.github.michaelbull.result.Result
|
||||
import com.github.michaelbull.result.map
|
||||
import org.springframework.web.bind.annotation.GetMapping
|
||||
import org.springframework.web.bind.annotation.PostMapping
|
||||
import org.springframework.web.bind.annotation.RequestBody
|
||||
import org.springframework.web.bind.annotation.RequestMapping
|
||||
import org.springframework.web.bind.annotation.RestController
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/api/products")
|
||||
class ProductController(
|
||||
private val productInputPort: ProductInputPort
|
||||
) {
|
||||
@GetMapping
|
||||
suspend fun getAll(): Result<List<ProductDto>, FunctionalError> = productInputPort.getAll()
|
||||
.map { products -> products.map(::ProductDto) }
|
||||
|
||||
@PostMapping
|
||||
suspend fun create(@RequestBody request: ProductCreationRequest): Result<Product, FunctionalError> =
|
||||
productInputPort.create(request.name, request.type)
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
package com.example.demo.exposition.product.core.advice
|
||||
|
||||
import org.springframework.web.bind.annotation.RestControllerAdvice
|
||||
|
||||
@RestControllerAdvice
|
||||
class ResultControllerAdvice {
|
||||
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
package com.example.demo.exposition.product.model
|
||||
|
||||
import com.example.demo.domain.product.model.ProductType
|
||||
|
||||
data class ProductCreationRequest(
|
||||
val name: String,
|
||||
val type: ProductType
|
||||
)
|
||||
@@ -0,0 +1,22 @@
|
||||
package com.example.demo.exposition.product.model
|
||||
|
||||
import com.example.demo.domain.product.model.Product
|
||||
import java.util.*
|
||||
|
||||
data class ProductDto(
|
||||
val id: UUID,
|
||||
val name: String,
|
||||
val type: ProductTypeDto
|
||||
) {
|
||||
constructor(product: Product): this(
|
||||
id = product.id,
|
||||
name = product.name,
|
||||
type = ProductTypeDto.fromDomain(product.type)
|
||||
)
|
||||
|
||||
fun toDomain() = Product(
|
||||
id,
|
||||
name,
|
||||
type = type.toDomain()
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
package com.example.demo.exposition.product.model
|
||||
|
||||
import com.example.demo.domain.product.model.ProductType
|
||||
|
||||
enum class ProductTypeDto {
|
||||
SOLID,
|
||||
LIQUID,
|
||||
GAS;
|
||||
|
||||
fun toDomain(): ProductType {
|
||||
return when (this) {
|
||||
SOLID -> ProductType.SOLID
|
||||
LIQUID -> ProductType.LIQUID
|
||||
GAS -> ProductType.GAS
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
fun fromDomain(productType: ProductType) = when (productType) {
|
||||
ProductType.SOLID -> SOLID
|
||||
ProductType.LIQUID -> LIQUID
|
||||
ProductType.GAS -> GAS
|
||||
}
|
||||
}
|
||||
}
|
||||
13
demo-infrastructure/build.gradle.kts
Normal file
13
demo-infrastructure/build.gradle.kts
Normal file
@@ -0,0 +1,13 @@
|
||||
plugins {
|
||||
kotlin("jvm")
|
||||
id("io.spring.dependency-management") version "1.1.7"
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation(kotlin("stdlib"))
|
||||
implementation(project(":demo-application"))
|
||||
implementation(project(":demo-domain"))
|
||||
implementation("org.springframework:spring-context")
|
||||
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.10.2")
|
||||
implementation("com.michael-bull.kotlin-result:kotlin-result-coroutines:2.0.1")
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
package com.example.demo.infrastructure.product
|
||||
|
||||
import com.example.demo.domain.core.error.TechnicalError
|
||||
import com.example.demo.domain.product.model.Product
|
||||
import com.example.demo.domain.product.model.ProductType.LIQUID
|
||||
import com.example.demo.domain.product.model.ProductType.SOLID
|
||||
import com.example.demo.domain.product.outputport.ProductOutputPort
|
||||
import com.github.michaelbull.result.Ok
|
||||
import com.github.michaelbull.result.Result
|
||||
import org.springframework.stereotype.Component
|
||||
import java.util.*
|
||||
|
||||
@Component
|
||||
class ProductInMemoryAdapter : ProductOutputPort {
|
||||
private val products = mutableListOf(
|
||||
Product(
|
||||
id = UUID.randomUUID(),
|
||||
name = "Banana x5",
|
||||
type = SOLID
|
||||
),
|
||||
Product(
|
||||
id = UUID.randomUUID(),
|
||||
name = "Banana juice 1L",
|
||||
type = LIQUID
|
||||
)
|
||||
)
|
||||
|
||||
override fun getById(id: UUID): Product? = products.find { product -> product.id == id }
|
||||
|
||||
override suspend fun getAll(): Result<List<Product>, TechnicalError> = Ok(products)
|
||||
|
||||
override fun save(product: Product): Result<Unit, TechnicalError> {
|
||||
products += product
|
||||
return Ok(Unit)
|
||||
}
|
||||
|
||||
override fun deleteById(id: UUID) {
|
||||
products.removeIf { product -> product.id == id }
|
||||
}
|
||||
}
|
||||
@@ -1,19 +1,22 @@
|
||||
plugins {
|
||||
kotlin("jvm")
|
||||
id("org.springframework.boot")
|
||||
id("io.spring.dependency-management")
|
||||
id("java")
|
||||
}
|
||||
|
||||
repositories {
|
||||
mavenCentral()
|
||||
id("org.springframework.boot") version "3.4.3"
|
||||
id("io.spring.dependency-management") version "1.1.7"
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation(kotlin("stdlib"))
|
||||
implementation(kotlin("reflect"))
|
||||
implementation(project(":demo-domain"))
|
||||
implementation(project(":demo-application"))
|
||||
implementation(project(":demo-infrastructure"))
|
||||
implementation(project(":demo-exposition"))
|
||||
implementation("org.springframework.boot:spring-boot-starter-web")
|
||||
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core")
|
||||
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-reactor")
|
||||
implementation("org.springframework.boot:spring-boot-starter-webflux")
|
||||
}
|
||||
|
||||
springBoot {
|
||||
mainClass = "com.example.demo.launcher.ApplicationLauncher"
|
||||
mainClass = "com.example.demo.launcher.ApplicationLauncherKt"
|
||||
}
|
||||
@@ -1,10 +1,19 @@
|
||||
package com.example.demo.launcher
|
||||
|
||||
import org.springframework.boot.autoconfigure.EnableAutoConfiguration
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication
|
||||
import org.springframework.boot.runApplication
|
||||
|
||||
@SpringBootApplication
|
||||
class DemoApplication
|
||||
@SpringBootApplication(
|
||||
scanBasePackages = [
|
||||
"com.example.demo.domain",
|
||||
"com.example.demo.application",
|
||||
"com.example.demo.infrastructure",
|
||||
"com.example.demo.exposition"
|
||||
]
|
||||
)
|
||||
@EnableAutoConfiguration
|
||||
open class DemoApplication
|
||||
|
||||
fun main(args: Array<String>) {
|
||||
runApplication<DemoApplication>(*args)
|
||||
|
||||
@@ -1,2 +1,6 @@
|
||||
rootProject.name = "demo"
|
||||
include(":demo-domain")
|
||||
include(":demo-application")
|
||||
include(":demo-infrastructure")
|
||||
include(":demo-exposition")
|
||||
include(":demo-launcher")
|
||||
@@ -1,11 +0,0 @@
|
||||
package com.example.demo
|
||||
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication
|
||||
import org.springframework.boot.runApplication
|
||||
|
||||
@SpringBootApplication
|
||||
class DemoApplication
|
||||
|
||||
fun main(args: Array<String>) {
|
||||
runApplication<DemoApplication>(*args)
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
spring.application.name=demo
|
||||
@@ -1,13 +0,0 @@
|
||||
package com.example.demo
|
||||
|
||||
import org.junit.jupiter.api.Test
|
||||
import org.springframework.boot.test.context.SpringBootTest
|
||||
|
||||
@SpringBootTest
|
||||
class DemoApplicationTests {
|
||||
|
||||
@Test
|
||||
fun contextLoads() {
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user