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.
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.
github.com/vektra/mockery
(installing onDockerfileTest
) which helps to auto-generate mock files based on the interfacesgithub.com/go-pg/pg
: Entity managergithub.com/google/uuid
: Generating and inspecting UUIDs based on RFC 4122 and DCE 1.1github.com/gorilla/handlers
: For logging HTTP requests in the Apache Combined Log Formatgithub.com/gorilla/mux
: HTTP router and URL matchergithub.com/tbaud0n/go-rql-parser
: For translating RQL (Resource Query Language) queries to SQLgithub.com/stretchr/testify
: Assertions and mock generation
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)
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
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.
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.
gorilla/mux
is used for routing of the HTTP handlers. Due to application architecture, it's easy to implement other servers, e.g. gRPC.
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%'
curl -X "POST" "http://localhost/recipes" \
-H 'Authorization: GoDDD' \
-d $'{
"name": "Dolma",
"difficulty": 4,
"is_vegetarian": false,
"prepare_time": 50
}'
curl -X "PUT" "http://localhost/recipes/{recipe_id}" \
-H 'Authorization: GoDDD' \
-d $'{
"name": "Dolma",
"difficulty": 4,
"is_vegetarian": false,
"prepare_time": 45
}'
curl -X "DELETE" "http://localhost/recipes/{recipe_id}" \
-H 'Authorization: GoDDD'
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
curl -X "POST" "http://localhost/recipes/{recipe_id}/rating" \
-d $'{
"value": 5
}'
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
)
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