This is a CRUD app for movies.
Launching the project requires Docker to be installed. Also, requests in the example section are made using curlie, so you may want to install it too (or use httpie, they have a similar syntax)
Run
docker compose up
to build and run the app and databases.
You can select with which database to run by setting MYAPP_DB
variable in .env
file; valid values are mongo
and postgres
.
To run tests:
go test ./...
$ curlie -k https://0.0.0.0:443/ping
HTTP/2 200
content-type: application/json; charset=utf-8
content-length: 16
date: Sat, 14 May 2022 22:06:47 GMT
{
"message": "OK"
}
$ curlie -k POST https://0.0.0.0:443/v1/movie title=Titanic year:=1997 country=USA
HTTP/2 201
content-type: application/json; charset=utf-8
content-length: 125
date: Sat, 14 May 2022 23:05:59 GMT
{
"title": "Titanic",
"year": 1997,
"country": "USA",
"id": "628035d74fb8f0fa0bf7b2f7",
"created_at": "2022-05-14T23:05:59.954Z"
}
$ curlie -k GET https://0.0.0.0:443/v1/movie/628035d74fb8f0fa0bf7b2f7
HTTP/2 200
content-type: application/json; charset=utf-8
content-length: 119
date: Sat, 14 May 2022 23:06:19 GMT
{
"title": "Titanic",
"year": 1997,
"country": "USA",
"id": "628035d74fb8f0fa0bf7b2f7",
"created_at": "2022-05-14T23:05:59.954Z"
}
$ curlie -k PUT https://0.0.0.0:443/v1/movie/628035d74fb8f0fa0bf7b2f7 title=Amelie year:=2001 country=France
HTTP/2 200
content-type: application/json; charset=utf-8
content-length: 16
date: Sat, 14 May 2022 23:32:32 GMT
{
"message": "OK"
}
$ curlie -k GET https://0.0.0.0:443/v1/movie/628035d74fb8f0fa0bf7b2f7
HTTP/2 200
content-type: application/json; charset=utf-8
content-length: 121
date: Sat, 14 May 2022 23:33:28 GMT
{
"title": "Amelie",
"year": 2001,
"country": "France",
"id": "628035d74fb8f0fa0bf7b2f7",
"created_at": "2022-05-14T23:05:59.954Z"
}
$ curlie -k DELETE https://0.0.0.0:443/v1/movie/628035d74fb8f0fa0bf7b2f7
HTTP/2 204
content-type: application/json; charset=utf-8
date: Sat, 14 May 2022 23:34:35 GMT
$ curlie -k GET https://0.0.0.0:443/v1/movie/628035d74fb8f0fa0bf7b2f7
HTTP/2 404
content-type: application/json; charset=utf-8
content-length: 27
date: Sat, 14 May 2022 23:34:51 GMT
{
"message": "no such movie"
}
The application is designed to follow the principles of DDD and inversion of
control, i.e. we have our domain layer in movie
and infrastructure layers
in other packages, which depend on entities from movie
and not vice versa.
├── cmd
├── internal
│ ├── movie # business logic/domain layer
│ ├── rest # HTTP layer
│ └── storage # DB layer
│ ├── mongo
│ └── postgres
└── pkg
└── must # just some utility functions
PUT
doesn't create new objects and can only update. It could've been implemented as anupsert
, but I thought that this was an unnecessary over-complication, especially given that IDs for objects are expected to be generated by the database and not the client.- Every layer contains its own model, which is an overkill
for this particular case as the application contains
almost no business logic, but I believe this approach
works well with services that are even a bit larger,
because it allows for more decoupling.
This is essentially a DTO pattern (but I think that
names like
movieJSON
are more idiomatic to Go thanmovieDTO
, which some people use) - In a real application I would store config files/values in a separate directory and source them using Viper, or at read them as CLI arguments or environment variables, but here all the values (ports, URLs, etc.) are just hardcoded. In production, I wouldn't do that, but here it's OK, I guess :)
- I have an
ErrWrongIDFormat
which is returned when an ID does not conform to MongoObjectID
orUUID
for Postgres format, but when this error occurs, the user only receives a404 Not Found
instead of some other error. I believe it's a good decision, because ID format is an implementation detail that doesn't matter to the user, but having an error that tells exactly what happened may come very useful when debugging or inspecting logs. - I've used moq for generating the movie repository mock, because it makes writing tests easier (you could read more about it in the author's post)
- Certificate is self-signed and I'm aware that the private key is exposed; but this is a home assignment :)