Presentation
Frontend
Service
Frontend
Controller
Delivery
List
Dto
Presentation
Frontend
Service
Frontend
Controller
Delivery
List
Dto
Tour
Consumer
Tour
Message
Presentation
Frontend
Service
Frontend
Controller
Logic
Delivery
List
Dto
Tour
Consumer
Tour
Message
Presentation
Frontend
Service
Frontend
Controller
Logic
Delivery
List
Dto
DeliveryList
Service
DeliveryList
Tour
Service
Tour
Tour
Consumer
Tour
Message
Presentation
Frontend
Service
Frontend
Controller
Delivery
List
Dto
DeliveryList
Service
DeliveryList
Tour
Service
Tour
Tour
Consumer
Tour
Message
Article
Service
Article
Logic
Presentation
Tour
Service
Tour
Tour
Consumer
Frontend
Service
Tour
Message
Frontend
Controller
Data
DeliveryList
Service
Article
Service
Article
DeliveryList
Delivery
List
Dto
depends on /
calls / knows
depends on /
calls / knows
Logic
Presentation
Tour
Service
Tour
Tour
Consumer
Tour
Repo.
Tour
Entity
Frontend
Service
Tour
Message
Frontend
Controller
DeliveryList
Service
Article
Service
ArticleData
ServiceClient
Article
ArticleData
Dto
DeliveryList
Delivery
List
Dto
depends on /
calls / knows
depends on /
calls / knows
Data
Logic
Presentation
Tour
Service
Tour
Tour
Consumer
Tour
Repo.
Tour
Entity
Frontend
Service
Tour
Message
Frontend
Controller
DeliveryList
Service
Article
Service
ArticleData
ServiceClient
Article
ArticleData
Dto
DeliveryList
Delivery
List
Dto
depends on /
calls / knows
depends on /
calls / knows
might "leak" into
Data
Logic
Presentation
might "leak" into
Tour
Service
Tour
Tour
Consumer
Tour
Repo.
Tour
Entity
Frontend
Service
Tour
Message
Frontend
Controller
DeliveryList
Service
Article
Service
ArticleData
ServiceClient
Article
ArticleData
Dto
DeliveryList
Delivery
List
Dto
Data
Logic
Presentation
Tour
Service
Tour
Tour
Repo.
Tour
Entity
Frontend
Service
Frontend
Controller
DeliveryList
Service
Article
Service
ArticleData
ServiceClient
Article
ArticleData
Dto
DeliveryList
Delivery
List
Dto
Tour
Consumer
Tour
Message
Data
Logic
Presentation
Tour
Service
Tour
Tour
Repo.
Tour
Entity
Frontend
Service
Frontend
Controller
DeliveryList
Service
Article
Service
ArticleData
ServiceClient
Article
ArticleData
Dto
DeliveryList
Delivery
List
Dto
Driving
Adapter
Data
Logic
Presentation
Tour
Consumer
Tour
Message
Tour
Service
Tour
Tour
Repo.
Tour
Entity
Frontend
Service
Frontend
Controller
DeliveryList
Service
Article
Service
ArticleData
ServiceClient
Article
ArticleData
Dto
DeliveryList
Delivery
List
Dto
Tour
Consumer
Tour
Message
Data
Logic
Presentation
Driving
Adapter
Tour
Service
Tour
Tour
Repo.
Tour
Entity
DeliveryList
Service
Article
Service
ArticleData
ServiceClient
Article
ArticleData
Dto
DeliveryList
Tour
Consumer
Tour
Message
Frontend
Service
Frontend
Controller
Delivery
List
Dto
Driving
Adapter
Driving
Adapter
Data
Logic
Presentation
Tour
Service
Tour
Tour
Repo.
Tour
Entity
Article
Service
ArticleData
ServiceClient
Article
ArticleData
Dto
Tour
Consumer
Tour
Message
Frontend
Service
Frontend
Controller
Delivery
List
Dto
DeliveryList
Service
DeliveryList
Application
Driving
Adapter
Driving
Adapter
Data
Logic
Tour
Service
Tour
Tour
Repo.
Tour
Entity
Article
Service
ArticleData
ServiceClient
Article
ArticleData
Dto
Tour
Consumer
Tour
Message
Frontend
Service
Frontend
Controller
Delivery
List
Dto
ToImport
Tour
DeliveryList
Service
DeliveryList
Application
Driving
Adapter
Driving
Adapter
Data
Logic
Driving
Port
knows nothing about
calls application via
Application
Driving
Adapter
Tour
Service
Tour
Tour
Repo.
Tour
Entity
DeliveryList
Service
Article
Service
ArticleData
ServiceClient
Article
ArticleData
Dto
DeliveryList
Tour
Consumer
Tour
Message
Frontend
Service
Delivery
List
Dto
ToImport
Tour
ToGet
DeliveryList
Frontend
Controller
Application
Driving
Adapter
Driving
Adapter
Data
Logic
Tour
Service
Tour
Tour
Repo.
Tour
Entity
DeliveryList
Service
Article
Service
ArticleData
ServiceClient
Article
ArticleData
Dto
DeliveryList
Tour
Consumer
Tour
Message
Frontend
Service
Delivery
List
Dto
ToImport
Tour
ToGet
DeliveryList
Frontend
Controller
Application
Driving
Adapter
Driving
Adapter
Data
Logic
Tour
Service
Tour
Tour
Repo.
Tour
Entity
DeliveryList
Service
Article
Service
Article
DeliveryList
Tour
Consumer
Tour
Message
Frontend
Service
Delivery
List
Dto
ToImport
Tour
ToGet
DeliveryList
Frontend
Controller
Application
Driving
Adapter
Driving
Adapter
Data
ArticleData
ServiceClient
ArticleData
Dto
Driven
Adapter
Tour
Service
Tour
Tour
Repo.
Tour
Entity
DeliveryList
Service
Article
Service
ArticleData
ServiceClient
Article
ArticleData
Dto
DeliveryList
Tour
Consumer
Tour
Message
Frontend
Service
Delivery
List
Dto
ToImport
Tour
ToGet
DeliveryList
Frontend
Controller
ToGet
Article
Application
Driving
Adapter
Driving
Adapter
Data
Driven
Adapter
Driven
Port
knows nothing about
calls driven adapter via
Driving
Port
knows nothing about
calls application via
Application
Driving
Adapter
Driven
Adapter
Tour
Service
Tour
Tour
Repo.
Tour
Entity
DeliveryList
Service
Article
Service
ArticleData
ServiceClient
Article
ArticleData
Dto
DeliveryList
Tour
Consumer
Tour
Message
Driving
Adapter
Frontend
Service
Delivery
List
Dto
ToImport
Tour
ToGet
DeliveryList
Frontend
Controller
Driving Port
Driven
Adapter
Application
Driven
Port
ToGet
Article
Application
Driving
Adapter
Driving
Adapter
Data
Driven
Adapter
DeliveryList
ArticleData
Dto
Tour
Tour
Service
DeliveryList
Service
Article
Article
Service
ArticleData
ServiceClient
Tour
Message
Tour
Consumer
Delivery
List
Dto
Frontend
Service
ToImport
Tour
ToGet
DeliveryList
Frontend
Controller
Tour
Entity
Tour
Repo.
ToGet
Article
Application
Driving
Adapter
Driving
Adapter
Driven
Adapter
Driven
Adapter
DeliveryList
ArticleData
Dto
Tour
Tour
Service
DeliveryList
Service
Article
Article
Service
ArticleData
ServiceClient
Tour
Message
Tour
Consumer
Delivery
List
Dto
Frontend
Service
ToImport
Tour
ToGet
DeliveryList
Frontend
Controller
Tour
Entity
Tour
Repo.
ToGet
Article
Application
Driving
Adapter
Driving
Adapter
Driven
Adapter
Driven
Adapter
@Repository
interface TourRepository
: CrudRepository<TourEntity, String>
@Service
class TourService(
private val repository: TourRepository) {
fun saveTour(tour: Tour) =
repository.save(TourEntity(tour))
// …
}
@Service
class TourStore(
private val repository: TourRepository) : ToStoreTour {
override fun storeTour(tour: Tour) =
repository.save(TourEntity(tour)).toTour()
}
DeliveryList
ArticleData
Dto
Tour
Tour
Service
DeliveryList
Service
Article
Article
Service
ArticleData
ServiceClient
Tour
Message
Tour
Consumer
Delivery
List
Dto
Frontend
Service
ToImport
Tour
ToGet
DeliveryList
Frontend
Controller
Tour
Entity
Tour
Repo.
ToGet
Article
Application
Driving
Adapter
Driving
Adapter
Driven
Adapter
Driven
Adapter
@Repository
interface TourRepository
: CrudRepository<TourEntity, String>
@Service
class TourService(
private val tourStore: ToStoreTour) {
fun storeTour(tour: Tour) =
tourStore.storeTour(tour)
// …
}
@Service
class TourStore(
private val repository: TourRepository) : ToStoreTour {
override fun storeTour(tour: Tour) =
repository.save(TourEntity(tour)).toTour()
}
DeliveryList
ArticleData
Dto
Tour
Tour
Service
Tour
Entity
DeliveryList
Service
Article
Article
Service
ArticleData
ServiceClient
Tour
Message
Tour
Consumer
Delivery
List
Dto
Frontend
Service
ToImport
Tour
ToGet
DeliveryList
Frontend
Controller
Tour
Repo.
ToStore
Tour
Tour
Store
ToGet
Article
Application
Driving
Adapter
Driving
Adapter
Driven
Adapter
Driven
Adapter
DeliveryList
ArticleData
Dto
Tour
Tour
Service
Tour
Entity
DeliveryList
Service
Article
Article
Service
ArticleData
ServiceClient
Tour
Message
Tour
Consumer
Delivery
List
Dto
Frontend
Service
ToImport
Tour
ToGet
DeliveryList
Frontend
Controller
ToGet
Article
Tour
Repo.
ToStore
Tour
Tour
Store
Application
Driving
Adapter
Driving
Adapter
Driven
Adapter
Driven
Adapter
Domain
<Service>
DemandService
<Data>
Demand
<InPort>
ToImportDemands
<OutPort>
ToStoreDemands
Application
Demand
Consumer
<Data>
Demand
Message
Demand
Store
Demand
Repo.
Demand
Entity
<Data>
Demand
Message
<Data>
Demand
Domain
<Service>
DemandService
<Data>
Demand
<InPort>
ToImportDemands
<OutPort>
ToStoreDemands
Application
Demand
Consumer
<Data>
Demand
Message
Demand
Store
Demand
Repo.
Demand
Entity
<Data>
Demand
Message
<Service>
DemandService
<Data>
Demand
<InPort>
ToImportDemands
<OutPort>
ToStoreDemands
Demand
Consumer
<Data>
Demand
Message
Demand
Store
Demand
Repo.
Demand
Entity
<Data>
Demand
Message
<Service>
DemandService
<Data>
Demand
<InPort>
ToImportDemands
<OutPort>
ToStoreDemands
Demand
Store
Demand
Repo.
Demand
Entity
Demand
Repo.Stub
<Service>
DemandService
<Data>
Demand
<InPort>
ToImportDemands
<OutPort>
ToStoreDemands
Demand
Store
class DemandRepositoryStub :
CrudRepositoryStub<DemandEntity, String>(),
DemandRepository {
fun findByOrderId(orderId: String) =
data.values.find {
entity -> demand.orderId == orderId
}
}
Demand
Repo.Stub
<Service>
DemandService
<Data>
Demand
<InPort>
ToImportDemands
<OutPort>
ToStoreDemands
Demand
Store
Demand
Repo.Stub
<Service>
DemandService
<Data>
Demand
<InPort>
ToImportDemands
<OutPort>
ToStoreDemands
Demand
Store
class DemandImporterTest {
private val demandStore : ToStoreDemands =
DemandStore(DemandRepositoryStub())
private val demandImporter : ToImportDemands =
DemandService(demandStore)
}
Demand
Repo.Stub
<Service>
DemandService
<Data>
Demand
<InPort>
ToImportDemands
<OutPort>
ToStoreDemands
Demand
Store
class DemandImporterTest {
private val demandStore : ToStoreDemands =
DemandStore(DemandRepositoryStub())
private val demandImporter : ToImportDemands =
DemandService(demandStore)
fun `importDemand`() {
val result = demandImporter.save(aDemand().build())
assertThat(result.isSuccessful()).isTrue()
assertThat(demandStore.findById(preStoredDemand.id))
.isNotNull()
}
}
Demand
Repo.Stub
<Service>
DemandService
<Data>
Demand
<OutPort>
ToStoreDemands
Demand
Store
<InPort>
ToImportDemands
class DemandImporterTest {
private val demandStore : ToStoreDemands =
DemandStore(DemandRepositoryStub())
private val demandImporter : ToImportDemands =
DemandService(demandStore)
fun `importDemand`() {
val result = demandImporter.save(aDemand().build())
assertThat(result.isSuccessful()).isTrue()
assertThat(demandStore.findById(preStoredDemand.id))
.isNotNull()
}
fun `importDemand conflict`() {
val preStoredDemand = aDemand().build()
demandStore.save(preStoredDemand)
val result = demandImporter.save(preStoredDemand)
assertThat(result.isConflict()).isTrue()
}
}
Demand
Repo.Stub
<Service>
DemandService
<Data>
Demand
<OutPort>
ToStoreDemands
Demand
Store
<InPort>
ToImportDemands
class DemandImporterTest {
private val demandStore : ToStoreDemands =
DemandStore(DemandRepositoryStub())
private val demandImporter : ToImportDemands =
DemandService(demandStore)
fun `importDemand`() {
val result = demandImporter.save(aDemand().build())
assertThat(result.isSuccessful()).isTrue()
assertThat(demandStore.findById(preStoredDemand.id))
.isNotNull()
}
fun `importDemand conflict`() {
val preStoredDemand = aDemand().build()
demandStore.save(preStoredDemand)
val result = demandImporter.save(preStoredDemand)
assertThat(result.isConflict()).isTrue()
}
fun `deleteDemand`() {
val preStoredDemand = aDemand().build()
demandStore.save(preStoredDemand))
val result = demandImporter.delete(preStoredDemand)
assertThat(result.isSuccessful()).isTrue()
assertThat(demandStore.findById(preStoredDemand.id))
.isNull()
}
}
Demand
Repo.Stub
<Service>
DemandService
<Data>
Demand
<OutPort>
ToStoreDemands
Demand
Store
<InPort>
ToImportDemands
class DemandImporterTest {
private val demandStore : ToStoreDemands =
DemandStore(DemandRepositoryStub())
private val demandImporter : ToImportDemands =
DemandService(demandStore)
fun `importDemand`() {
val result = demandImporter.save(aDemand().build())
assertThat(result.isSuccessful()).isTrue()
assertThat(demandStore.findById(preStoredDemand.id))
.isNotNull()
}
fun `importDemand conflict`() {
val preStoredDemand = aDemand().build()
demandStore.save(preStoredDemand)
val result = demandImporter.save(preStoredDemand)
assertThat(result.isConflict()).isTrue()
}
fun `deleteDemand`() {
val preStoredDemand = aDemand().build()
demandStore.save(preStoredDemand))
val result = demandImporter.delete(preStoredDemand)
assertThat(result.isSuccessful()).isTrue()
assertThat(demandStore.findById(preStoredDemand.id))
.isNull()
}
}
Demand
Repo.Stub
<Service>
DemandService
<Data>
Demand
<OutPort>
ToStoreDemands
Demand
Store
<InPort>
ToImportDemands
class DemandImporterTest {
private val demandStore : ToStoreDemands =
DemandStore(DemandRepositoryStub())
private val demandImporter : ToImportDemands =
DemandService(demandStore)
fun `importDemand`() {
val result = demandImporter.save(aDemand().build())
assertThat(result.isSuccessful()).isTrue()
assertThat(demandStore.findById(preStoredDemand.id))
.isNotNull()
}
fun `importDemand conflict`() {
val preStoredDemand = aDemand().build()
demandStore.save(preStoredDemand)
val result = demandImporter.save(preStoredDemand)
assertThat(result.isConflict()).isTrue()
}
fun `deleteDemand`() {
val preStoredDemand = aDemand().build()
demandStore.save(preStoredDemand))
val result = demandImporter.delete(preStoredDemand)
assertThat(result.isSuccessful()).isTrue()
assertThat(demandStore.findById(preStoredDemand.id))
.isNull()
}
}
class DemandBuilder private constructor {
private var id: String = UUID.randomUUID().toString()
private var version: Long = 1
private var referenceId: String = randomReferenceId()
// …
companion object {
fun aDemand() = DemandBuilder()
}
fun id(id: String) = apply { this.id = id }
fun version(version: Long) = apply { this.version = version }
fun referenceId(referenceId: String) = apply {
this.referenceId = referenceId }
// …
fun build() = Demand(
id = id,
version = version,
referenceId = referenceId,
// …
)
}
Demand
Repo.Stub
<Service>
DemandService
<Data>
Demand
<OutPort>
ToStoreDemands
Demand
Store
<InPort>
ToImportDemands
Domain
<Service>
DemandService
<Data>
Demand
<InPort>
ToImportDemands
<OutPort>
ToStoreDemands
Application
Demand
Consumer
<Data>
Demand
Message
Demand
Store
Demand
Repo.
Demand
Entity
<Data>
Demand
Message
<InPort>
ToImportDemands
<OutPort>
ToStoreDemands
Demand
Consumer
<Data>
Demand
Message
Demand
Store
Demand
Repo.
Demand
Entity
<Data>
Demand
Message
Demand
Entity
<Data>
Demand
Message
<InPort>
ToImportDemands
<OutPort>
ToStoreDemands
Demand
Consumer
<Data>
Demand
Message
Demand
Store
Demand
Repo.
Demand
Entity
<Data>
Demand
Message
Domain
<Service>
DemandService
<Data>
Demand
<InPort>
ToImportDemands
<OutPort>
ToStoreDemands
Application
Demand
Consumer
<Data>
Demand
Message
Demand
Store
Demand
Repo.
Demand
Entity
<Data>
Demand
Message
Domain
<Service>
DemandService
<Data>
Demand
<InPort>
ToImportDemands
<OutPort>
ToStoreDemands
Application
Demand
Consumer
<Data>
Demand
Message
Demand
Store
Demand
Repo.
Demand
Entity
<Data>
Demand
Message
Domain
<Service>
DemandService
<Data>
Demand
<OutPort>
ToStoreDemands
Application
Demand
Consumer
<Data>
Demand
Message
Demand
Store
Demand
Repo.
Demand
Entity
<Data>
Demand
Message
@SpringBootTest
@EmbeddedKafka
class DemandCosumptionTest() : AbstractServiceTest {
}
<InPort>
ToImportDemands
Domain
<Service>
DemandService
<Data>
Demand
<OutPort>
ToStoreDemands
Application
Demand
Consumer
<Data>
Demand
Message
Demand
Store
Demand
Repo.
Demand
Entity
<Data>
Demand
Message
@SpringBootTest
@EmbeddedKafka
class DemandCosumptionTest(
@Autowired private val demandProducer: DemandProducer,
) : AbstractServiceTest {
}
<InPort>
ToImportDemands
@Component
class DemandProducer(
@Autowired kafkaTemplate: KafkaTemplate<String, String>,
@Value("\${kafka.topics.demand.name}") topicName: String
) {
fun send() {
val result: CompletableFuture<SendResult<String, String>> =
kafkaTemplate.send(topic, key, data)
kafkaTemplate.flush()
result.get(10, SECONDS)
}
}
Domain
<Service>
DemandService
<Data>
Demand
<OutPort>
ToStoreDemands
Application
Demand
Consumer
<Data>
Demand
Message
Demand
Store
Demand
Repo.
Demand
Entity
<Data>
Demand
Message
@SpringBootTest
@EmbeddedKafka
class DemandCosumptionTest(
@Autowired private val demandProducer: DemandProducer,
@Autowired private val demandStore: ToStoreDemands
) : AbstractServiceTest {
}
<InPort>
ToImportDemands
Domain
<Service>
DemandService
<Data>
Demand
<OutPort>
ToStoreDemands
Application
Demand
Consumer
<Data>
Demand
Message
Demand
Store
Demand
Repo.
Demand
Entity
<Data>
Demand
Message
@SpringBootTest
@EmbeddedKafka
class DemandCosumptionTest(
@Autowired private val demandProducer: DemandProducer,
@Autowired private val demandStore: ToStoreDemands
) : AbstractServiceTest {
fun `consume demand`() {
val demandMessage = aDemandMessage().build()
demandProducer.send(demandMessage)
await().untilAsserted {
assertThat(demandStore.findById(demandMessage.id))
.isNotNull()
}
}
}
<InPort>
ToImportDemands
Domain
<Service>
DemandService
<Data>
Demand
<OutPort>
ToStoreDemands
Application
Demand
Consumer
<Data>
Demand
Message
Demand
Store
Demand
Repo.
Demand
Entity
<Data>
Demand
Message
@SpringBootTest
@EmbeddedKafka
class DemandCosumptionTest(
@Autowired private val demandProducer: DemandProducer,
@Autowired private val demandStore: ToStoreDemands
) : AbstractServiceTest {
fun `consume demand`() {
val demandMessage = aDemandMessage().build()
demandProducer.send(demandMessage)
await().untilAsserted {
assertThat(demandStore.findById(demandMessage.id))
.isNotNull()
}
}
fun `consume tombstone`() {
val preStoredDemand = aDemand().build()
demandStore.save(preStoredDemand)
demandProducer.sendTombstone(preStoredDemand.id)
await().untilAsserted {
assertThat(demandStore.findById(demandMessage.id))
.isNull()
}
}
}
<InPort>
ToImportDemands
Domain
<Service>
DemandService
<Data>
Demand
<InPort>
ToImportDemands
<OutPort>
ToStoreDemands
Application
Demand
Consumer
<Data>
Demand
Message
Demand
Store
Demand
Repo.
Demand
Entity
<Data>
Demand
Message
Domain
Application
Very well and easily covered by simple Unit Tests
Testing port-to-port
Requires stubbing
Test data builders
Still fast and simple unit tests
Mostly about infrastructure
Unit Tests rather pointless
Focussed Integration Tests or Service Tests necessary
@SpringBootTest
Testcontainers
ports
application
adapters
SpringBootApplication
driven
driving
domain
driven
driving