goddd
goddd copied to clipboard
Sample Go application with DDD
GoDDD (Recipes API)
This repository is sample recipe service which comes with CRUD and rating the recipes endpoints. This sample is just for giving an idea how to implement the DDD in Go.
Implementation Notes
Domain Driven Design (DDD) is an approach which is followed for this sample. It's open to discussion whether we need it or not because there is no super complexity in our domain but it's good approach for abstraction.
In this example we have 3 layers:
- Application - Main entry point which contains endpoints (or commands), middleware, etc.
- Domain - Encapsulated business logic
- Infrastructure - Database, 3rd party sources, etc.
Packages
-
github.com/vektra/mockery
(installing onDockerfileTest
) which helps to auto-generate mock files based on the interfaces -
github.com/go-pg/pg
: Entity manager -
github.com/google/uuid
: Generating and inspecting UUIDs based on RFC 4122 and DCE 1.1 -
github.com/gorilla/handlers
: For logging HTTP requests in the Apache Combined Log Format -
github.com/gorilla/mux
: HTTP router and URL matcher -
github.com/tbaud0n/go-rql-parser
: For translating RQL (Resource Query Language) queries to SQL -
github.com/stretchr/testify
: Assertions and mock generation
Setup
You can just run docker-compose up -d
and wait a couple of seconds that app became ready to handle the requests (or you can run docker-compose up
and watch the logs to know when the app is ready)
Test
For running the tests
docker-compose run test
Test service is using DockerfileTest
which is generating mock files and running all tests with coverage.
Furthermore, Do not forget to run flyway
service up before running the tests because we need a database with tables for integration tests
P.S. The mock files are not added to the pull request to keep the repository clean. You can use the commands for generating mock files:
$ go get github.com/vektra/mockery/.../
$ cd /path/to/project
$ mockery -dir domain/repository -name RecipeRepository
$ mockery -dir domain/repository -name RatingRepository
$ mockery -dir domain/interactor -name RecipeInteractor
$ mockery -dir domain/tool -name IdGenerator
Database migration
Flyway is used as a database migration tool. It helps us to not stick on one language because whenever we have the decision to switch to another language then we do not need to care about database migration.
Furthermore, The 1st initial migration script is added to the repository which creates the tables and fills some dummy data.
Authorization
For making create
, update
, delete
endpoints protected I implemented just hardcoded GoDDD
token but in current application design it is easy to switch to some other verification. E.g. JWT.
Presenter
gorilla/mux
is used for routing of the HTTP handlers. Due to application architecture, it's easy to implement other servers, e.g. gRPC.
Filtering
Resource Query Language (RQL) is used for filtering. Supported operators:
- scalar operators
-
=
: equal -
=ne=
: not equal -
=like=
: contain -
=match=
: contain (case-insensitive) -
=gt=
: greater -
=lt=
: less -
=ge=
: greater equal -
=le=
: less equal
-
- logic operators
-
,
or&
: and -
;
or|
: or
-
For like
and match
operators you should use *
instead of %
. E.g. name=match=*goddd*
translates to name ILIKE '%goddd%'
Examples
Create
curl -X "POST" "http://localhost/recipes" \
-H 'Authorization: GoDDD' \
-d $'{
"name": "Dolma",
"difficulty": 4,
"is_vegetarian": false,
"prepare_time": 50
}'
Update
curl -X "PUT" "http://localhost/recipes/{recipe_id}" \
-H 'Authorization: GoDDD' \
-d $'{
"name": "Dolma",
"difficulty": 4,
"is_vegetarian": false,
"prepare_time": 45
}'
Delete
curl -X "DELETE" "http://localhost/recipes/{recipe_id}" \
-H 'Authorization: GoDDD'
Get
curl "http://localhost/recipes/0d93bda0-f040-4564-967b-e59bf5571dcd"
P.S This endpoint returns one extra rating
field which is an overall rating of the recipe. E.g. in case of 2 ratings (5
& 4
) it will return 4.5
Rate
curl -X "POST" "http://localhost/recipes/{recipe_id}/rating" \
-d $'{
"value": 5
}'
List
curl "http://localhost/recipes/?filter={conditions}&page={page_number}&limit={recipe_per_page}"
You can pass 3 (optional) query params to the recipe list endpoint
-
filter
: basic RQL filtering. E.g.prepare_time==15;name=like=*kebab*
will return the recipes names which contains%kebab%
and prepare time is15
-
limit
: for deciding the count of the recipes per page (default:10
) -
page
: current page number of pagination (default:1
)
Missing parts
Only important parts are covered by tests. Some of them are not fully covered but easy to cover.
- Not %100 covered by unit tests
- Not %100 covered by integration tests