Giter Site home page Giter Site logo

banking-kata-java's Introduction

Banking Kata - Java

CI

Overview

This project illustrates TDD & Clean Architecture implementation in Java, showing the Use Case Driven Development Approach.

This demo was created for the purposes of meetup series on TDD & Clean Architecture. See the YouTube Meetups. Please note that this project is purely for demo purposes only.

Business Requirements

We implement a Banking system with the following use cases:

  • Open account
  • Withdraw funds
  • Deposit funds
  • View account

We also have authentication with Keycloak - a realm with client_id and with client_credentials flow enabled.

Architecture

The overall architecture is split into:

  • Application Core (core) layer contains the business logic.
  • Adapter (adapter) layer contains the infrastructural concerns.

The Application Core(core) is composed of:

  • Ports (ports), representing an interface to the Application Core, isolating it from infrastructural concerns. There are both Driver (driver) ports, representing use cases; and Driven (driven) ports, representing gateways.
  • Internals (internal), representing the internal implementation of the Application Core, more specifically, the implementation of the Driver (driver) ports. This implementation can be anything. Here we illustrate the implementation with Clean Architecture (cleanarch) approach and the CRUD (crud) approach, but other approaches are possible too. The use case tests are independent of the implementation approach; the internal implementation is thus swappable.

The Adapter (adapter) layer is composed of driver adapters and driven adapters.

  • Driver adapters: REST API Adapter (adapter-restapi-*)
  • Driven adapters: Persistence Adapters (adapter-persistence-*), Random Number Generation Adapters (adapter-generation-*), Time Adapters (adapter-time-*), Third Party Integration Adapters (adapter-thirdparty-*), and Messaging Adapters (adapter-messaging-*).

The application can be executed via startup.

Tests

  • Core Layer: Unit Tests targeting the Driver Ports.
  • Adapter Layer: Integration Tests targeting the Driven Ports. In cases of integrating with third-party systems or microservices, we use Contract Testing.

As can be seen below, you can separately run these fast-running and slow-running tests.

Prerequisites

  • OpenJDK 17
  • Docker

Running Gradle

Make sure that Gradle works:

./gradlew

Environment Setup

Remove old images/volumes:

docker-compose down -v

Apply the environment variables (Windows):

. .\env\env.ps1

Apply the environment variables (for Linux/Mac):

source ./env/env.sh

Apply the environment variable in the ide, in case if it needs to run the application from ide UI button For intellij, there is a plugin EnvFile can help to run the env file.

#chmod 777 ./env/env.intellij.ui
Then select the Run->Edit configuration which will then provide the option to import and run the file.

For Mac only, you need to build a custom Keycloak image to enable Keycloak to work on Mac M1. This is due to a reported Mac-specific issue docker/for-mac#5310. For any other OS, please skip this step, because this issue is Mac-specific:

cd startup
chmod +x ./src/main/resources/keycloak/build-keycloak-image-m1.zsh
./src/main/resources/keycloak/build-keycloak-image-m1.zsh 16.0.0

Run docker:

docker-compose up -d

verify if rabbitmq admin console is accessible and the docker image is running without issues

http://localhost:15672
user: admin
pass: admin

Running Unit Tests

Run unit tests for core:

./gradlew coreTest

Running Integration Tests

As a prerequisite, please see Environment Setup.

To run all the adapter integration tests:

./gradlew adapterTest

Note: Currently, this fails locally due to open issues with Flyway & MongoDB( #111 and #114).

Running System Tests

As a prerequisite, please see Environment Setup.

To run the whole system tests:

./gradlew systemTest

Note: Currently, this fails locally due to open issues with Flyway & MongoDB( #111 and #114).

Running all Tests

As a prerequisite, please see Environment Setup.

In the above instructions, we ran the tests separately. You can run them all:

./gradlew test

Note: Currently, this fails locally due to open issues with Flyway & MongoDB( #111 and #114).

Running Code Coverage

Run code coverage (executes Jacoco):

./gradlew codeCoverage

To run it for specific project:

./gradlew core:codeCoverage

Running Mutation Testing

Run mutation testing (executes pitest):

./gradlew mutationTest

Viewing Reports

See the build\reports directory for the generated reports for test results, code coverage and mutation testing.

Reports:

  • build\reports\tests
  • build\reports\jacoco
  • build\reports\pitest

Running Spring Boot

To manually run the app.

./gradlew runApp

Then CTRL+C to terminate the app.

Optional Notes

The following are for additional reading, you do not need to execute these, but you can if you wish.

Environment variables are located inside the env folder. You can optionally choose to edit them.

You can choose to run the tests via IntelliJ UI.

In the case of integration tests (for adapters) you'd have to specify environment variables before you run the tests. To do that, you can copy the text from the file env/env.intellij.ui into the Environment variables section into Tests in 'banking-kata.adapters' configuration.

To run Docker with the environment file:

docker-compose --env-file=env/.env.local up -d

You can run the integration tests individually, e.g. if you modified a module (in this way you avoid waiting for all of them to finish):

./gradlew adapter-persistence-jpa:test

For code coverage, the underlying call is:

./gradlew jacocoTestReport

For mutation testing, the underlying call is:

./gradlew pitest

For flyway operation from CLI: There will be flyway tasks available in the gradle as the plugin is applied to adapter-persistence-jpa. Tasks can be executed by the following way -

./gradlew adapter-persistence-jpa:flywayInfo  //will provide the schema file under flywayInfo
./gradlew adapter-persistence-jpa:flywayClean //will clear up the schema from database
./gradlew adapter-persistence-jpa:flywayMigrate //will apply the db schema migration into database

Issues

If you experience Integration Tests failing, please see the following known issue #64.

If you experience any other issues, please create a ticket https://github.com/valentinacupac/banking-kata-java/issues/new

Contributors

Our contributors are:

Contributing

If you'd like to contribute, see instructions here https://github.com/valentinacupac/banking-kata-java/blob/main/CONTRIBUTING.md

banking-kata-java's People

Contributors

adrianliz avatar donaldsiziba avatar eamtalu avatar f-lombardo avatar guillaumetaffin avatar joaocipriano avatar julianojj avatar valentinacupac avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

banking-kata-java's Issues

ViewAccountUseCase - Show account score

Account score is calculated as follows:

  • NameFactor => count the total number of characters in the account holder's first name and last name (let's call it "n"). If that is <= 5 then the NameFactor is 3, if it's > 5 and <= 10 then the NameFactor is "n", and if it's > 10 then the NameFactor is 3 x "n".

  • BalanceFactor => the balance factor is the value of the balance + 5.

  • TimeFactor => the time factor is calculated as the number of days since the account was created

Then the score is:

  • Take the sum: NameFactor + BalanceFactor - TimeFactor
  • If the result is > 0, then if it's an odd number, the score is "A"; if it's an even number, the score is "B". If the result is <= 0, then the score is "C".

Modularization - Restrict imports in gradle to a specific package to ensure hexagonal architecture boundaries

Hello,

Last weekend I tried to add on the project a plugin in order to banned some imports according to some packages. This can be very useful to check at the build step if we violated boundaries of the hexagonal architecture. For example thanks to this plugin, I can ensure that we can't have imports from infrastructure in domain package.

On top of that, if it's the case, we can add some exclusions to make explicit the todo list of what should be shift and ensure that no new banned imports will be added.

Maven Plugin:

Little problem, when I opened the banking-cata-java project, I was surprised because it is not a maven project but a gradle project.
And for gradle I don't know how I can use this useful plugin.

  • I tried to call maven plugin in gradle but this seems to be weird.
  • According to this https://kordamp.org/enforcer-gradle-plugin/#_excludedependencies and this https://github.com/kordamp/enforcer-gradle-plugin, I tried to add a root configuration in settings.gradle but I don't know how I can exclude dependencies for a specific package (may be if you know some groovy tips) and I tried to add several build.gradle project to have specific configuration according to those sub packages but it's not working like this, indeed, a sub gradle project it's like a module maven and we can't apply this to only separate packages (because domain and infra are in the same project).

So if you have an idea on how we can have a similar check by using gradle I will be happy to talk with you :)

see below a maven sample to exclude infra imports in domain package :

					<execution>
						<id>check-imports</id>
						<phase>process-sources</phase>
						<goals>
							<goal>enforce</goal>
						</goals>
						<configuration>
							<rules>
								<RestrictImports>
									<groups>
										<group>
											<reason>Forbid to depend to infrastructure/application (adapters primary/secondary) or external lib in domain</reason>
											<basePackages>
												<basePackage>com.example.demo.domain.**</basePackage>
											</basePackages>
											<bannedImports>
												<bannedImport>com.example.demo.adapters.primaries.**</bannedImport>
												<bannedImport>com.example.demo.adapters.secondaries.**</bannedImport>
												<bannedImport>com.fasterxml.jackson.**</bannedImport>
												<bannedImport>org.springframework.**</bannedImport>
												<bannedImport>io.swagger.**</bannedImport>
												<bannedImport>lombok.**</bannedImport>
											</bannedImports>
											<exclusions>
											</exclusions>
										</group>
                                                                                    <group>
                                                                                      <reason>Forbid to depend to infrastructure (secondary) in application (primary)</reason>
                                                                                      <basePackages>
                                                                                        <basePackage>com.example.demo.adapters.primaries.**</basePackage>
                                                                                      </basePackages>
                                                                                      <bannedImports>
                                                                                        <bannedImport>com.example.demo.adapters.secondaries.**</bannedImport>
                                                                                      </bannedImports>
                                                                                      <exclusions>
                                                                                      </exclusions>
                                                                                    </group>
                                                                                    <group>
                                                                                      <reason>Limit dependency of demo to the other domains (useful in case of modular monolith)</reason>
                                                                                      <basePackages>
                                                                                        <basePackage>com.example.demo.**</basePackage>
                                                                                      </basePackages>
                                                                                      <bannedImports>
                                                                                        <bannedImport>com.example.business.**</bannedImport>
                                                                                      </bannedImports>
                                                                                    </group>
									</groups>
								</RestrictImports>

								<!-- You could have another rule instance here for restricting further imports -->
							</rules>
						</configuration>
					</execution> 

Database tests failing on GitHub Actions

DB tests are passing locally, but failing on GitHub Actions after changes in a69b455

GitHub Actions log https://github.com/valentinacupac/banking-kata-java/actions/runs/3788279403/jobs/6440898243#step:8:86

`BankingApplicationTests > contextLoads() FAILED
java.lang.IllegalStateException at DefaultCacheAwareContextLoaderDelegate.java:132
Caused by: java.lang.IllegalStateException at SpringBootCondition.java:60
Caused by: java.lang.IllegalArgumentException at PropertyPlaceholderHelper.java:180

JpaBankAccountStorageTest > should_return_empty_given_non_existent_account_number() FAILED
org.junit.jupiter.api.extension.ParameterResolutionException at ExecutableInvoker.java:239
Caused by: java.lang.IllegalStateException at DefaultCacheAwareContextLoaderDelegate.java:132
Caused by: java.lang.IllegalStateException at SpringBootCondition.java:60
Caused by: java.lang.IllegalArgumentException at PropertyPlaceholderHelper.java:180

JpaBankAccountStorageTest > should_return_added_bank_account() FAILED
org.junit.jupiter.api.extension.ParameterResolutionException at ExecutableInvoker.java:239
Caused by: java.lang.IllegalStateException at DefaultCacheAwareContextLoaderDelegate.java:132
Caused by: java.lang.IllegalStateException at SpringBootCondition.java:60
Caused by: java.lang.IllegalArgumentException at PropertyPlaceholderHelper.java:180

JpaBankAccountStorageTest > should_find_multiple_added_bank_accounts() FAILED
org.junit.jupiter.api.extension.ParameterResolutionException at ExecutableInvoker.java:239
Caused by: java.lang.IllegalStateException at DefaultCacheAwareContextLoaderDelegate.java:132
Caused by: java.lang.IllegalStateException at SpringBootCondition.java:60
Caused by: java.lang.IllegalArgumentException at PropertyPlaceholderHelper.java:180

BankAccountControllerSystemTest > should_open_account_given_valid_request() FAILED
org.junit.jupiter.api.extension.ParameterResolutionException at ExecutableInvoker.java:239
Caused by: java.lang.IllegalStateException at DefaultCacheAwareContextLoaderDelegate.java:132
Caused by: java.lang.IllegalStateException at SpringBootCondition.java:60
Caused by: java.lang.IllegalArgumentException at PropertyPlaceholderHelper.java:180

108 tests completed, 5 failed`

Pending to see if this is caused due to Postgres password change? Aside from that, there weren't any other changes related to Postgres.

Locally, the tests pass (when Postgres environment variables are configured), the issue started appearing on GitHub Actions only.

KeyCloak - Connection Error

PROBLEM DESCRIPTION

BankAccountControllerSystemTest test fails after the changeset with Keycloak addition a69b455

I executed the instructions as per running_with_docker.md.

I get this response:

> Task :test

BankAccountControllerSystemTest > should_open_account_given_valid_request() FAILED
    org.springframework.web.reactive.function.client.WebClientRequestException at ExchangeFunctions.java:141
        Caused by: io.netty.channel.AbstractChannel$AnnotatedConnectException
            Caused by: java.net.ConnectException at Net.java:-2
2022-12-27 17:56:02.448  INFO 29688 --- [ionShutdownHook] j.LocalContainerEntityManagerFactoryBean : Closing JPA EntityManagerFactory for persistence unit 'default'
2022-12-27 17:56:02.449  INFO 29688 --- [ionShutdownHook] com.zaxxer.hikari.HikariDataSource       : HikariPool-1 - Shutdown initiated...
2022-12-27 17:56:02.452  INFO 29688 --- [ionShutdownHook] com.zaxxer.hikari.HikariDataSource       : HikariPool-1 - Shutdown completed.
2022-12-27 17:56:02.462  INFO 29688 --- [ionShutdownHook] j.LocalContainerEntityManagerFactoryBean : Closing JPA EntityManagerFactory for persistence unit 'default'
2022-12-27 17:56:02.464  INFO 29688 --- [ionShutdownHook] com.zaxxer.hikari.HikariDataSource       : HikariPool-2 - Shutdown initiated...
2022-12-27 17:56:02.465  INFO 29688 --- [ionShutdownHook] com.zaxxer.hikari.HikariDataSource       : HikariPool-2 - Shutdown completed.
2022-12-27 17:56:02.472  INFO 29688 --- [ionShutdownHook] j.LocalContainerEntityManagerFactoryBean : Closing JPA EntityManagerFactory for persistence unit 'default'
2022-12-27 17:56:02.473  INFO 29688 --- [ionShutdownHook] com.zaxxer.hikari.HikariDataSource       : HikariPool-3 - Shutdown initiated...
2022-12-27 17:56:02.474  INFO 29688 --- [ionShutdownHook] com.zaxxer.hikari.HikariDataSource       : HikariPool-3 - Shutdown completed.

108 tests completed, 1 failed

> Task :test FAILED

FAILURE: Build failed with an exception.

Reviewing the more detailed stack trace

org.springframework.web.reactive.function.client.WebClientRequestException: Connection refused: no further information: localhost/127.0.0.1:10000; nested exception is io.netty.channel.AbstractChannel$AnnotatedConnectException: Connection refused: no further information: localhost/127.0.0.1:10000
	at app//org.springframework.web.reactive.function.client.ExchangeFunctions$DefaultExchangeFunction.lambda$wrapException$9(ExchangeFunctions.java:141)
	at app//reactor.core.publisher.MonoErrorSupplied.subscribe(MonoErrorSupplied.java:55)
	at app//reactor.core.publisher.Mono.subscribe(Mono.java:4400)
	at app//reactor.core.publisher.FluxOnErrorResume$ResumeSubscriber.onError(FluxOnErrorResume.java:103)
	at app//reactor.core.publisher.FluxPeek$PeekSubscriber.onError(FluxPeek.java:222)
	at app//reactor.core.publisher.FluxPeek$PeekSubscriber.onError(FluxPeek.java:222)
	at app//reactor.core.publisher.FluxMap$MapSubscriber.onError(FluxMap.java:132)
	at app//reactor.core.publisher.FluxPeek$PeekSubscriber.onError(FluxPeek.java:222)
	at app//reactor.core.publisher.MonoNext$NextSubscriber.onError(MonoNext.java:93)
	at app//reactor.core.publisher.MonoFlatMapMany$FlatMapManyMain.onError(MonoFlatMapMany.java:204)
	at app//reactor.core.publisher.SerializedSubscriber.onError(SerializedSubscriber.java:124)
	at app//reactor.core.publisher.FluxRetryWhen$RetryWhenMainSubscriber.whenError(FluxRetryWhen.java:225)
	at app//reactor.core.publisher.FluxRetryWhen$RetryWhenOtherSubscriber.onError(FluxRetryWhen.java:274)
	at app//reactor.core.publisher.FluxConcatMap$ConcatMapImmediate.drain(FluxConcatMap.java:415)
	at app//reactor.core.publisher.FluxConcatMap$ConcatMapImmediate.onNext(FluxConcatMap.java:251)
	at app//reactor.core.publisher.EmitterProcessor.drain(EmitterProcessor.java:491)
	at app//reactor.core.publisher.EmitterProcessor.tryEmitNext(EmitterProcessor.java:299)
	at app//reactor.core.publisher.SinkManySerialized.tryEmitNext(SinkManySerialized.java:100)
	at app//reactor.core.publisher.InternalManySink.emitNext(InternalManySink.java:27)
	at app//reactor.core.publisher.FluxRetryWhen$RetryWhenMainSubscriber.onError(FluxRetryWhen.java:190)
	at app//reactor.core.publisher.MonoCreate$DefaultMonoSink.error(MonoCreate.java:194)
	at app//reactor.netty.http.client.HttpClientConnect$MonoHttpConnect$ClientTransportSubscriber.onError(HttpClientConnect.java:304)
	at app//reactor.core.publisher.MonoCreate$DefaultMonoSink.error(MonoCreate.java:194)
	at app//reactor.netty.resources.DefaultPooledConnectionProvider$DisposableAcquire.onError(DefaultPooledConnectionProvider.java:155)
	at app//reactor.core.publisher.FluxContextWrite$ContextWriteSubscriber.onError(FluxContextWrite.java:121)
	at app//reactor.netty.internal.shaded.reactor.pool.AbstractPool$Borrower.fail(AbstractPool.java:477)
	at app//reactor.netty.internal.shaded.reactor.pool.SimpleDequePool.lambda$drainLoop$9(SimpleDequePool.java:431)
	at app//reactor.core.publisher.FluxDoOnEach$DoOnEachSubscriber.onError(FluxDoOnEach.java:186)
	at app//reactor.core.publisher.MonoCreate$DefaultMonoSink.error(MonoCreate.java:194)
	at app//reactor.netty.resources.DefaultPooledConnectionProvider$PooledConnectionAllocator$PooledConnectionInitializer.onError(DefaultPooledConnectionProvider.java:538)
	at app//reactor.core.publisher.MonoFlatMap$FlatMapMain.secondError(MonoFlatMap.java:192)
	at app//reactor.core.publisher.MonoFlatMap$FlatMapInner.onError(MonoFlatMap.java:259)
	at app//reactor.core.publisher.FluxOnErrorResume$ResumeSubscriber.onError(FluxOnErrorResume.java:106)
	at app//reactor.core.publisher.Operators.error(Operators.java:198)
	at app//reactor.core.publisher.MonoError.subscribe(MonoError.java:53)
	at app//reactor.core.publisher.Mono.subscribe(Mono.java:4400)
	at app//reactor.core.publisher.FluxOnErrorResume$ResumeSubscriber.onError(FluxOnErrorResume.java:103)
	at app//reactor.netty.transport.TransportConnector$MonoChannelPromise.tryFailure(TransportConnector.java:534)
	at app//reactor.netty.transport.TransportConnector$MonoChannelPromise.setFailure(TransportConnector.java:488)
	at app//reactor.netty.transport.TransportConnector.lambda$doConnect$7(TransportConnector.java:223)
	at app//io.netty.util.concurrent.DefaultPromise.notifyListener0(DefaultPromise.java:578)
	at app//io.netty.util.concurrent.DefaultPromise.notifyListeners0(DefaultPromise.java:571)
	at app//io.netty.util.concurrent.DefaultPromise.notifyListenersNow(DefaultPromise.java:550)
	at app//io.netty.util.concurrent.DefaultPromise.notifyListeners(DefaultPromise.java:491)
	at app//io.netty.util.concurrent.DefaultPromise.setValue0(DefaultPromise.java:616)
	at app//io.netty.util.concurrent.DefaultPromise.setFailure0(DefaultPromise.java:609)
	at app//io.netty.util.concurrent.DefaultPromise.tryFailure(DefaultPromise.java:117)
	at app//io.netty.channel.nio.AbstractNioChannel$AbstractNioUnsafe.fulfillConnectPromise(AbstractNioChannel.java:321)
	at app//io.netty.channel.nio.AbstractNioChannel$AbstractNioUnsafe.finishConnect(AbstractNioChannel.java:337)
	at app//io.netty.channel.nio.NioEventLoop.processSelectedKey(NioEventLoop.java:710)
	at app//io.netty.channel.nio.NioEventLoop.processSelectedKeysOptimized(NioEventLoop.java:658)
	at app//io.netty.channel.nio.NioEventLoop.processSelectedKeys(NioEventLoop.java:584)
	at app//io.netty.channel.nio.NioEventLoop.run(NioEventLoop.java:496)
	at app//io.netty.util.concurrent.SingleThreadEventExecutor$4.run(SingleThreadEventExecutor.java:986)
	at app//io.netty.util.internal.ThreadExecutorMap$2.run(ThreadExecutorMap.java:74)
	at app//io.netty.util.concurrent.FastThreadLocalRunnable.run(FastThreadLocalRunnable.java:30)
	at [email protected]/java.lang.Thread.run(Thread.java:833)
	Suppressed: java.lang.Exception: #block terminated with an error
		at reactor.core.publisher.BlockingSingleSubscriber.blockingGet(BlockingSingleSubscriber.java:139)
		at reactor.core.publisher.Mono.block(Mono.java:1731)
		at org.springframework.test.web.reactive.server.DefaultWebTestClient$DefaultRequestBodyUriSpec.exchange(DefaultWebTestClient.java:361)
		at com.optivem.kata.banking.system.BankAccountControllerSystemTest.getToken(BankAccountControllerSystemTest.java:52)
		at com.optivem.kata.banking.system.BankAccountControllerSystemTest.should_open_account_given_valid_request(BankAccountControllerSystemTest.java:67)
		at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
		at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77)
		at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
		at java.base/java.lang.reflect.Method.invoke(Method.java:568)
		at org.junit.platform.commons.util.ReflectionUtils.invokeMethod(ReflectionUtils.java:725)
		at org.junit.jupiter.engine.execution.MethodInvocation.proceed(MethodInvocation.java:60)
		at org.junit.jupiter.engine.execution.InvocationInterceptorChain$ValidatingInvocation.proceed(InvocationInterceptorChain.java:131)
		at org.junit.jupiter.engine.extension.TimeoutExtension.intercept(TimeoutExtension.java:149)
		at org.junit.jupiter.engine.extension.TimeoutExtension.interceptTestableMethod(TimeoutExtension.java:140)
		at org.junit.jupiter.engine.extension.TimeoutExtension.interceptTestMethod(TimeoutExtension.java:84)
		at org.junit.jupiter.engine.execution.ExecutableInvoker$ReflectiveInterceptorCall.lambda$ofVoidMethod$0(ExecutableInvoker.java:115)
		at org.junit.jupiter.engine.execution.ExecutableInvoker.lambda$invoke$0(ExecutableInvoker.java:105)
		at org.junit.jupiter.engine.execution.InvocationInterceptorChain$InterceptedInvocation.proceed(InvocationInterceptorChain.java:106)
		at org.junit.jupiter.engine.execution.InvocationInterceptorChain.proceed(InvocationInterceptorChain.java:64)
		at org.junit.jupiter.engine.execution.InvocationInterceptorChain.chainAndInvoke(InvocationInterceptorChain.java:45)
		at org.junit.jupiter.engine.execution.InvocationInterceptorChain.invoke(InvocationInterceptorChain.java:37)
		at org.junit.jupiter.engine.execution.ExecutableInvoker.invoke(ExecutableInvoker.java:104)
		at org.junit.jupiter.engine.execution.ExecutableInvoker.invoke(ExecutableInvoker.java:98)
		at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.lambda$invokeTestMethod$7(TestMethodTestDescriptor.java:214)
		at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
		at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.invokeTestMethod(TestMethodTestDescriptor.java:210)
		at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.execute(TestMethodTestDescriptor.java:135)
		at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.execute(TestMethodTestDescriptor.java:66)
		at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$6(NodeTestTask.java:151)
		at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
		at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$8(NodeTestTask.java:141)
		at org.junit.platform.engine.support.hierarchical.Node.around(Node.java:137)
		at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$9(NodeTestTask.java:139)
		at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
		at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:138)
		at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:95)
		at java.base/java.util.ArrayList.forEach(ArrayList.java:1511)
		at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.invokeAll(SameThreadHierarchicalTestExecutorService.java:41)
		at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$6(NodeTestTask.java:155)
		at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
		at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$8(NodeTestTask.java:141)
		at org.junit.platform.engine.support.hierarchical.Node.around(Node.java:137)
		at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$9(NodeTestTask.java:139)
		at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
		at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:138)
		at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:95)
		at java.base/java.util.ArrayList.forEach(ArrayList.java:1511)
		at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.invokeAll(SameThreadHierarchicalTestExecutorService.java:41)
		at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$6(NodeTestTask.java:155)
		at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
		at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$8(NodeTestTask.java:141)
		at org.junit.platform.engine.support.hierarchical.Node.around(Node.java:137)
		at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$9(NodeTestTask.java:139)
		at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
		at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:138)
		at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:95)
		at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.submit(SameThreadHierarchicalTestExecutorService.java:35)
		at org.junit.platform.engine.support.hierarchical.HierarchicalTestExecutor.execute(HierarchicalTestExecutor.java:57)
		at org.junit.platform.engine.support.hierarchical.HierarchicalTestEngine.execute(HierarchicalTestEngine.java:54)
		at org.junit.platform.launcher.core.EngineExecutionOrchestrator.execute(EngineExecutionOrchestrator.java:107)
		at org.junit.platform.launcher.core.EngineExecutionOrchestrator.execute(EngineExecutionOrchestrator.java:88)
		at org.junit.platform.launcher.core.EngineExecutionOrchestrator.lambda$execute$0(EngineExecutionOrchestrator.java:54)
		at org.junit.platform.launcher.core.EngineExecutionOrchestrator.withInterceptedStreams(EngineExecutionOrchestrator.java:67)
		at org.junit.platform.launcher.core.EngineExecutionOrchestrator.execute(EngineExecutionOrchestrator.java:52)
		at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:114)
		at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:86)
		at org.junit.platform.launcher.core.DefaultLauncherSession$DelegatingLauncher.execute(DefaultLauncherSession.java:86)
		at org.junit.platform.launcher.core.SessionPerRequestLauncher.execute(SessionPerRequestLauncher.java:53)
		at org.gradle.api.internal.tasks.testing.junitplatform.JUnitPlatformTestClassProcessor$CollectAllTestClassesExecutor.processAllTestClasses(JUnitPlatformTestClassProcessor.java:99)
		at org.gradle.api.internal.tasks.testing.junitplatform.JUnitPlatformTestClassProcessor$CollectAllTestClassesExecutor.access$000(JUnitPlatformTestClassProcessor.java:79)
		at org.gradle.api.internal.tasks.testing.junitplatform.JUnitPlatformTestClassProcessor.stop(JUnitPlatformTestClassProcessor.java:75)
		at org.gradle.api.internal.tasks.testing.SuiteTestClassProcessor.stop(SuiteTestClassProcessor.java:61)
		at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
		at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77)
		at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
		at java.base/java.lang.reflect.Method.invoke(Method.java:568)
		at org.gradle.internal.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:36)
		at org.gradle.internal.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:24)
		at org.gradle.internal.dispatch.ContextClassLoaderDispatch.dispatch(ContextClassLoaderDispatch.java:33)
		at org.gradle.internal.dispatch.ProxyDispatchAdapter$DispatchingInvocationHandler.invoke(ProxyDispatchAdapter.java:94)
		at jdk.proxy2/jdk.proxy2.$Proxy5.stop(Unknown Source)
		at org.gradle.api.internal.tasks.testing.worker.TestWorker$3.run(TestWorker.java:193)
		at org.gradle.api.internal.tasks.testing.worker.TestWorker.executeAndMaintainThreadName(TestWorker.java:129)
		at org.gradle.api.internal.tasks.testing.worker.TestWorker.execute(TestWorker.java:100)
		at org.gradle.api.internal.tasks.testing.worker.TestWorker.execute(TestWorker.java:60)
		at org.gradle.process.internal.worker.child.ActionExecutionWorker.execute(ActionExecutionWorker.java:56)
		at org.gradle.process.internal.worker.child.SystemApplicationClassLoaderWorker.call(SystemApplicationClassLoaderWorker.java:133)
		at org.gradle.process.internal.worker.child.SystemApplicationClassLoaderWorker.call(SystemApplicationClassLoaderWorker.java:71)
		at worker.org.gradle.process.internal.worker.GradleWorkerMain.run(GradleWorkerMain.java:69)
		at worker.org.gradle.process.internal.worker.GradleWorkerMain.main(GradleWorkerMain.java:74)
Caused by: io.netty.channel.AbstractChannel$AnnotatedConnectException: Connection refused: no further information: localhost/127.0.0.1:10000
Caused by: java.net.ConnectException: Connection refused: no further information
	at java.base/sun.nio.ch.Net.pollConnect(Native Method)
	at java.base/sun.nio.ch.Net.pollConnectNow(Net.java:672)
	at java.base/sun.nio.ch.SocketChannelImpl.finishConnect(SocketChannelImpl.java:946)
	at io.netty.channel.socket.nio.NioSocketChannel.doFinishConnect(NioSocketChannel.java:330)
	at io.netty.channel.nio.AbstractNioChannel$AbstractNioUnsafe.finishConnect(AbstractNioChannel.java:334)
	at io.netty.channel.nio.NioEventLoop.processSelectedKey(NioEventLoop.java:710)
	at io.netty.channel.nio.NioEventLoop.processSelectedKeysOptimized(NioEventLoop.java:658)
	at io.netty.channel.nio.NioEventLoop.processSelectedKeys(NioEventLoop.java:584)
	at io.netty.channel.nio.NioEventLoop.run(NioEventLoop.java:496)
	at io.netty.util.concurrent.SingleThreadEventExecutor$4.run(SingleThreadEventExecutor.java:986)
	at io.netty.util.internal.ThreadExecutorMap$2.run(ThreadExecutorMap.java:74)
	at io.netty.util.concurrent.FastThreadLocalRunnable.run(FastThreadLocalRunnable.java:30)
	at java.base/java.lang.Thread.run(Thread.java:833)

Similar failure currently occurs on GitHub Actions too https://github.com/valentinacupac/banking-kata-java/actions/runs/3788816924/jobs/6441972612

BankAccountControllerSystemTest > should_open_account_given_valid_request() FAILED
    org.springframework.web.reactive.function.client.WebClientRequestException at ExchangeFunctions.java:1[41](https://github.com/valentinacupac/banking-kata-java/actions/runs/3788816924/jobs/6441972612#step:8:42)
        Caused by: io.netty.channel.AbstractChannel$AnnotatedConnectException
            Caused by: java.net.ConnectException at Errors.java:155
2022-12-27 16:54:10.0[47](https://github.com/valentinacupac/banking-kata-java/actions/runs/3788816924/jobs/6441972612#step:8:48)  INFO 3365 --- [ionShutdownHook] j.LocalContainerEntityManagerFactoryBean : Closing JPA EntityManagerFactory for persistence unit 'default'
2022-12-27 16:54:10.0[49](https://github.com/valentinacupac/banking-kata-java/actions/runs/3788816924/jobs/6441972612#step:8:50)  INFO 3365 --- [ionShutdownHook] com.zaxxer.hikari.HikariDataSource       : HikariPool-1 - Shutdown initiated...
2022-12-27 16:54:10.0[53](https://github.com/valentinacupac/banking-kata-java/actions/runs/3788816924/jobs/6441972612#step:8:54)  INFO 3365 --- [ionShutdownHook] com.zaxxer.hikari.HikariDataSource       : HikariPool-1 - Shutdown completed.

108 tests completed, 1 failed
2022-12-27 16:[54](https://github.com/valentinacupac/banking-kata-java/actions/runs/3788816924/jobs/6441972612#step:8:55):10.070  INFO 3365 --- [ionShutdownHook] j.LocalContainerEntityManagerFactoryBean : Closing JPA EntityManagerFactory for persistence unit 'default'
2022-12-27 16:54:10.071  INFO 3365 --- [ionShutdownHook] com.zaxxer.hikari.HikariDataSource       : HikariPool-2 - Shutdown initiated...
2022-12-27 16:54:10.073  INFO 3365 --- [ionShutdownHook] com.zaxxer.hikari.HikariDataSource       : HikariPool-2 - Shutdown completed.
2022-12-27 16:54:10.081  INFO 3365 --- [ionShutdownHook] j.LocalContainerEntityManagerFactoryBean : Closing JPA EntityManagerFactory for persistence unit 'default'
2022-12-27 16:54:10.081  INFO 3365 --- [ionShutdownHook] com.zaxxer.hikari.HikariDataSource       : HikariPool-3 - Shutdown initiated...
2022-12-27 16:54:10.085  INFO 3365 --- [ionShutdownHook] com.zaxxer.hikari.HikariDataSource       : HikariPool-3 - Shutdown completed.

HOTFIX

I have temporarily commented out the security-related code (see commit ef5ba9c) because BankAccountControllerSystemTest.should_open_account_given_valid_request was failing both locally & on GitHub Actions. That code could be brought back later, after we find out how to fix this issue to make the test pass.

Communication - Synchronous communication with external services

A reality we can often observe in real world applications, and even more so in microservices architectures, is communication with multiple services in order to achieve a task.
For example, in case of opening a new account, checking that a person exists and is registered in the country.

It would be useful to add an example of communication with a fictional external system to demonstrate the implementation with hexagonal architecture (hint: basically a repository).

Fix Mutation Testing - Adding Environment Variables to Gradle Pitest Plugin

Mutation testing used to work, but stopped working after the addition of environment variables

The error is:

`
4:46:45 PM PIT >> SEVERE : Tests failing without mutation:
Description [testClass=com.optivem.kata.banking.BankingApplicationTests, name=[engine:junit-jupiter]/[class:com.optivem.kata.banking.BankingApplicationTests]/[method:contextLoads()]]
Exception in thread "main" org.pitest.help.PitHelpError: 1 tests did not pass without mutation when calculating line coverage. Mutation testing requires a green suite.
See http://pitest.org for more details.
at org.pitest.coverage.execute.DefaultCoverageGenerator.verifyBuildSuitableForMutationTesting(DefaultCoverageGenerator.java:115)
at org.pitest.coverage.execute.DefaultCoverageGenerator.calculateCoverage(DefaultCoverageGenerator.java:97)
at org.pitest.coverage.execute.DefaultCoverageGenerator.calculateCoverage(DefaultCoverageGenerator.java:52)
at org.pitest.mutationtest.tooling.MutationCoverage.runAnalysis(MutationCoverage.java:149)
at org.pitest.mutationtest.tooling.MutationCoverage.runReport(MutationCoverage.java:139)
at org.pitest.mutationtest.tooling.EntryPoint.execute(EntryPoint.java:123)
at org.pitest.mutationtest.tooling.EntryPoint.execute(EntryPoint.java:54)
at org.pitest.mutationtest.commandline.MutationCoverageReport.runReport(MutationCoverageReport.java:98)
at org.pitest.mutationtest.commandline.MutationCoverageReport.main(MutationCoverageReport.java:45)

`

I was searching for "Gradle Pitest Plugin Environment Variables"

I tried to set the environment variables, based on this https://stackoverflow.com/questions/56159077/set-environment-variables-in-gradle-pitest-plugin

tasks.named('pitest') { environment('env:POSTGRES_URL', 'jdbc:postgresql://localhost:5432/banking_kata') environment('$POSTGRES_USER', 'postgres') environment('POSTGRES_PASSWORD', 'admin') }

But still doesn't work.

Plan to continue further search.

Modularization - JVM Test Suite - Separate Test Execution

  • Separation of execution of unit tests versus integration tests
  • Only integration tests should require DB installation, environment variables, etc...
  • Docker compose needed only for integration tests
  • Perhaps also separate the system/e2e tests?
  • Shared test fixtures

Adapter - Persistence - MongoDB

Adding a secondary implementation for BankAccountRepository with MongoDB.
Make it configurable so that we can switch between them.

WithdrawFundsUseCase - MainFlow - Deduct amount

  1. User provides existent bank account id
  2. User provides valid withdraw amount (should be <= balance)
  3. System deducts the withdrawal amount
  4. System saves the changes in a persistence mechanism
  5. System shows the updated balance to the user

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.