This is an interview app made to share my knowledge & experience with development. Also for fun. Mostly for fun. You can test the project directly here: https://blackjack.ivsk.net
docker compose up
docker run -d -p 8080:8080 --network=host --name nav-blackjack-backend ivanskodje1/nav-blackjack-backend:master
docker run -d -p 3000:3000 --network=host --name nav-blackjack-frontend ivanskodje1/nav-blackjack-frontend:master
- Visit the frontend page @ http://localhost:3000
- Call the backend API directly @ http://localhost:8080/api/play
- See source code @ https://github.com/ivanskodje/nav-blackjack
These images automatically updates with changes done on GitHub
See /.github/workflows
This project uses GitHub action to automatically build docker images separately for nav-blackjack-backend and nav-blackjack-frontend.
This is the backend, which is coded using Spring Boot framework with Kotlin.
Initial Spring Boot project was created using spring initializer, and features:
- OpenFeign to easily communicate with external APIs
- API for simulating a gameplay:
/api/play
- API for getting the Magnus' game rules:
/api/gamerules
- Spring Boot 3.2.2 framework
- Spring Boot Web for exposing our own API
- Spring Cloud's OpenFeign for communicating with external APIs
- Kotest for testing
# Build
cd nav-blackjack-backend
mvn clean install
# Run (with default)
java -jar target/nav-blackjack-backend.jar
# Alt: Run (run with custom endpoint and get request)
java -jar target/nav-blackjack-backend.jar --feign.blackjack.endpoint="https://blackjack.ekstern.dev.nav.no" --feign.blackjack.get.shuffle="/shuffle"
You can customize the external API endpoint by passing in
FEIGN_BLACKJACK_ENDPOINT
and/orFEIGN_BLACKJACK_GET_SHUFFLE
. For example:docker run ... -e FEIGN_BLACKJACK_ENDPOINT=https://your-external-api -e FEIGN_BLACKJACK_GET_SHUFFLE=/newshuffle ...
# Build
cd nav-blackjack-backend
docker build -t nav-blackjack-backend:local .
# Run
docker run -it -p 8080:8080 --network=host --name nav-blackjack-backend nav-blackjack-backend:local
Returns the simulated game results for each player, as well as declaring a winner.
Linux:
curl -s 'http://localhost:8080/api/play'
Windows:
curl.exe -s 'http://localhost:8080/api/play'
{
"winner": "You",
"players": [
{
"name": "You",
"score": 19,
"cards": [
{
"suit": "H",
"value": 10
},
{
"suit": "D",
"value": 9
}
]
},
{
"name": "Magnus",
"score": 29,
"cards": [
{
"suit": "D",
"value": 11
},
{
"suit": "S",
"value": 8
},
{
"suit": "H",
"value": 10
},
{
"suit": "H",
"value": 10
}
]
}
]
}
Returns a String list with the custom Magnus-rules this game follows
Linux:
curl -s 'http://localhost:8080/api/gamerules'
Windows:
curl.exe -s 'http://localhost:8080/api/gamerules'
[
"The game is played between two players, you and Magnus.",
"Each player starts with two cards from a randomly shuffled deck.",
"You are dealt the first two cards, Magnus receives the next two.",
"Points are calculated where numbered cards equal their face value, face cards (Jack, Queen, King) are 10 points, and Aces are 11 points.",
"If a player gets 21 points with initial cards, they win (Blackjack).",
"If no player has 21 points initially, both players draw cards from the deck.",
"You stop drawing cards when your total points are 17 or more.",
"If your points exceed 21, you lose.",
"Magnus starts drawing cards after you stop.",
"Magnus stops drawing when his points are higher than yours.",
"If Magnus's points exceed 21, he loses."
]
This app is following the hexagonal architectural pattern, also known as "Ports and Adapters". The reason we do so, is because this architectural pattern is easy to work with and maintain, and scales very well. It makes change easy, and decouples business logic away from implementation patterns (business don't care "how" you solve a problem).
Adapters can depend on both Application and Domain, but generally also uses their own models in order to expose breaking changes early.
Adapters are the entry and exit components of our service. Nothing enters or leaves our application without going through one of these adapters. Adapters depends on the ports to access our system. This helps reduce coupling, which in turns greatly enhances testability,maintainability, and flexibility.
Ports are usually interfaces that are implemented by an adapter, but a port can also be services or facades for ingoing requests. For example, a Rest Controller would most likely use a Service or Facade directly, but a Database adapter would implement an interface, such as a Repository interface.
The application only depends on itself and the domain
This is the core business logic. The Application components does not depend on any adapters, only on ports and the domain objects.
The domain only depends on itself.
This contains the domain language. These are objects and components that do not contain any outside dependencies. These may contain independent logic that only speaks the domain language. In our case, we have a Player that will always have a hand of cards. You can ask the player how many cards it have, or what the score is.
I couldn't help myself, I wanted to make a frontend to pair with the backend. ๐
- Next.js 14 Framework
- Tailwind CSS & Aksel for UI/UX
- TypeScript
IMPORTANT: This depends on nav-blackjack-backend
# Build
cd nav-blackjack-frontend
yarn
# Start
yarn dev
# Build
cd nav-blackjack-frontend
npm install
# Start
npm run dev
# Build
docker build -t nav-blackjack-frontend:local .
# Run
docker run -e BACKEND_BASE_URL=http://nav-blackjack-backend:8080 --network=host -it -p 3000:3000 nav-blackjack-frontend:local