-
Notifications
You must be signed in to change notification settings - Fork 8
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(wild-workouts): add slimmed down wild-workouts-go-ddd-example repo
This repo originates from `threedotlabs` to accompany their "Introducing Clean Architecture" blog post found [here](https://threedots.tech/post/introducing-clean-architecture/). Take a few moments... gather your thoughts about this slimmed down example. Question: What positivie aspects do you find in this design pattern? Question: What concerns do you have with this design pattern? Refs: [Threedots Tech Introduction to Clean Architecture](https://threedots.tech/post/introducing-clean-architecture/)
- Loading branch information
Showing
66 changed files
with
8,131 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
# Created by .ignore support plugin (hsz.mobi) | ||
### Go template | ||
# Binaries for programs and plugins | ||
*.exe | ||
*.exe~ | ||
*.dll | ||
*.so | ||
*.dylib | ||
|
||
# Test binary, built with `go test -c` | ||
*.test | ||
|
||
# Output of the go coverage tool, specifically when used with LiteIDE | ||
*.out | ||
|
||
# Dependency directories (remove the comment below to include it) | ||
vendor/ | ||
|
||
# Tools | ||
.idea | ||
|
||
.terraform | ||
*.tfstate | ||
*.tfstate.*backup | ||
terraform/.env | ||
terraform/cloud-builders-community | ||
|
||
/web/src/repositories/clients/* | ||
!/web/src/repositories/clients/trainer/src/ | ||
!/web/src/repositories/clients/trainings/src/ | ||
!/web/src/repositories/clients/users/src/ | ||
|
||
/service-account-file.json | ||
|
||
.go/ | ||
.go-cache/ |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
MIT License | ||
|
||
Copyright (c) 2021 Three Dots Labs | ||
|
||
Permission is hereby granted, free of charge, to any person obtaining a copy | ||
of this software and associated documentation files (the "Software"), to deal | ||
in the Software without restriction, including without limitation the rights | ||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | ||
copies of the Software, and to permit persons to whom the Software is | ||
furnished to do so, subject to the following conditions: | ||
|
||
The above copyright notice and this permission notice shall be included in all | ||
copies or substantial portions of the Software. | ||
|
||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | ||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | ||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | ||
SOFTWARE. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,48 @@ | ||
include .env | ||
export | ||
|
||
.PHONY: openapi | ||
openapi: openapi_http openapi_js | ||
|
||
.PHONY: openapi_http | ||
openapi_http: | ||
@./scripts/openapi-http.sh trainer internal/trainer/ports ports | ||
@./scripts/openapi-http.sh trainings internal/trainings/ports ports | ||
@./scripts/openapi-http.sh users internal/users main | ||
|
||
.PHONY: openapi_js | ||
openapi_js: | ||
@./scripts/openapi-js.sh trainer | ||
@./scripts/openapi-js.sh trainings | ||
@./scripts/openapi-js.sh users | ||
|
||
.PHONY: proto | ||
proto: | ||
@./scripts/proto.sh trainer | ||
@./scripts/proto.sh users | ||
|
||
.PHONY: lint | ||
lint: | ||
@go-cleanarch | ||
@./scripts/lint.sh common | ||
@./scripts/lint.sh trainer | ||
@./scripts/lint.sh trainings | ||
@./scripts/lint.sh users | ||
|
||
.PHONY: fmt | ||
fmt: | ||
goimports -l -w internal/ | ||
|
||
.PHONY: mycli | ||
mycli: | ||
mycli -u ${MYSQL_USER} -p ${MYSQL_PASSWORD} ${MYSQL_DATABASE} | ||
|
||
.PHONY: c4 | ||
c4: | ||
cd tools/c4 && go mod tidy && sh generate.sh | ||
|
||
test: | ||
@./scripts/test.sh common .e2e.env | ||
@./scripts/test.sh trainer .test.env | ||
@./scripts/test.sh trainings .test.env | ||
@./scripts/test.sh users .test.env |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,107 @@ | ||
# Wild Workouts | ||
|
||
# NOTE: this is adapated from the Wild Workouts Project from threedot labs | ||
# to address some thoughts doing things the Java way. | ||
|
||
Wild Workouts is an **example Go DDD** project that we created to show how to build Go applications that are **easy to develop, maintain, and fun to work with, especially in the long term!** | ||
|
||
*The idea for this series, is to apply DDD by refactoring. This process is in progress! Please check articles, to know the current progress.* | ||
|
||
No application is perfect from the beginning. With over a dozen coming articles, we will uncover what issues you can find in the current implementation. We will also show how to fix these issues and achieve clean implementation by refactoring. | ||
|
||
### Articles | ||
|
||
#### "Too modern" application | ||
|
||
1. [**Too modern Go application? Building a serverless application with Google Cloud Run and Firebase**](https://threedots.tech/post/serverless-cloud-run-firebase-modern-go-application/?utm_source=github.com) _[[v1.0]](https://github.com/ThreeDotsLabs/wild-workouts-go-ddd-example/releases/tag/v1.0)_ | ||
2. [**A complete Terraform setup of a serverless application on Google Cloud Run and Firebase**](https://threedots.tech/post/complete-setup-of-serverless-application/?utm_source=github.com) _[[v1.1]](https://github.com/ThreeDotsLabs/wild-workouts-go-ddd-example/releases/tag/v1.1)_ | ||
3. [**Robust gRPC communication on Google Cloud Run (but not only!)**](https://threedots.tech/post/robust-grpc-google-cloud-run/?utm_source=github.com) _[[v1.2]](https://github.com/ThreeDotsLabs/wild-workouts-go-ddd-example/releases/tag/v1.2)_ | ||
4. [**You should not build your own authentication. Let Firebase do it for you.**](https://threedots.tech/post/firebase-cloud-run-authentication/?utm_source=github.com) _[[v1.3]](https://github.com/ThreeDotsLabs/wild-workouts-go-ddd-example/releases/tag/v1.3)_ | ||
|
||
#### Refactoring | ||
|
||
5. [**Business Applications in Go: Things to know about DRY**](https://threedots.tech/post/things-to-know-about-dry/?utm_source=github.com) _[[v2.0]](https://github.com/ThreeDotsLabs/wild-workouts-go-ddd-example/releases/tag/v2.0)_ | ||
6. [**When microservices in Go are not enough: introduction to DDD Lite**](https://threedots.tech/post/ddd-lite-in-go-introduction/?utm_source=github.com) _[[v2.1]](https://github.com/ThreeDotsLabs/wild-workouts-go-ddd-example/releases/tag/v2.1)_ | ||
7. [**Repository pattern: painless way to simplify your Go service logic**](https://threedots.tech/post/repository-pattern-in-go/?utm_source=github.com) _[[v2.2]](https://github.com/ThreeDotsLabs/wild-workouts-go-ddd-example/releases/tag/v2.2)_ | ||
8. [**4 practical principles of high-quality database integration tests in Go**](https://threedots.tech/post/database-integration-testing/?utm_source=github.com) _[[v2.3]](https://github.com/ThreeDotsLabs/wild-workouts-go-ddd-example/releases/tag/v2.3)_ | ||
9. [**Introducing Clean Architecture by refactoring a Go project**](https://threedots.tech/post/introducing-clean-architecture/?utm_source=github.com) _[[v2.4]](https://github.com/ThreeDotsLabs/wild-workouts-go-ddd-example/releases/tag/v2.4)_ | ||
10. [**Introducing basic CQRS by refactoring**](https://threedots.tech/post/basic-cqrs-in-go/?utm_source=github.com) _[[v2.5]](https://github.com/ThreeDotsLabs/wild-workouts-go-ddd-example/releases/tag/v2.5)_ | ||
11. [**Combining DDD, CQRS, and Clean Architecture**](https://threedots.tech/post/ddd-cqrs-clean-architecture-combined/?utm_source=github.com) | ||
12. [**Microservices test architecture. Can you sleep well without end-to-end tests?**](https://threedots.tech/post/microservices-test-architecture/?utm_source=github.com) _[[v2.6]](https://github.com/ThreeDotsLabs/wild-workouts-go-ddd-example/releases/tag/v2.6)_ | ||
13. [**Repository secure by design: how to sleep better without fear of security vulnerabilities**](https://threedots.tech/post/repository-secure-by-design/?utm_source=github.com) | ||
14. [**Running integration tests on Google Cloud Build using docker-compose**](https://threedots.tech/post/running-integration-tests-on-google-cloud-build/?utm_source=github.com) _[[v2.7]](https://github.com/ThreeDotsLabs/wild-workouts-go-ddd-example/releases/tag/v2.7)_ | ||
15. *More articles are on the way!* | ||
|
||
### Community | ||
|
||
We're building a Discord community focused on modern business applications. It's the place to discuss hard topics, request a review, or ask if something's not clear. [Come join us!](https://discord.gg/kTVsGjPYDn) | ||
|
||
### Directories | ||
|
||
- [api](api/) OpenAPI and gRPC definitions | ||
- [docker](docker/) Dockerfiles | ||
- [internal](internal/) application code | ||
- [scripts](scripts/) deployment and development scripts | ||
- [terraform](terraform/) - infrastructure definition | ||
- [web](web/) - frontend JavaScript code | ||
|
||
### Live Demo | ||
|
||
The example application is available at [https://threedotslabs-wildworkouts.web.app/](https://threedotslabs-wildworkouts.web.app/). | ||
|
||
### Running locally | ||
|
||
```go | ||
> docker-compose up | ||
|
||
# ... | ||
|
||
web_1 | INFO Starting development server... | ||
web_1 | DONE Compiled successfully in 6315ms11:18:26 AM | ||
web_1 | | ||
web_1 | | ||
web_1 | App running at: | ||
web_1 | - Local: http://localhost:8080/ | ||
web_1 | | ||
web_1 | It seems you are running Vue CLI inside a container. | ||
web_1 | Access the dev server via http://localhost:<your container's external mapped port>/ | ||
web_1 | | ||
web_1 | Note that the development build is not optimized. | ||
web_1 | To create a production build, run yarn build. | ||
``` | ||
|
||
### Google Cloud Deployment | ||
|
||
```go | ||
> cd terraform/ | ||
> make | ||
|
||
Fill all required parameters: | ||
project [current: wild-workouts project]: # <----- put your Wild Workouts Google Cloud project name here (it will be created) | ||
user [current: email@gmail.com]: # <----- put your Google (Gmail, G-suite etc.) e-mail here | ||
billing_account [current: My billing account]: # <----- your billing account name, can be found here https://console.cloud.google.com/billing | ||
region [current: europe-west1]: | ||
firebase_location [current: europe-west]: | ||
|
||
# it may take a couple of minutes... | ||
|
||
The setup is almost done! | ||
|
||
Now you need to enable Email/Password provider in the Firebase console. | ||
To do this, visit https://console.firebase.google.com/u/0/project/[your-project]/authentication/providers | ||
|
||
You can also downgrade the subscription plan to Spark (it's set to Blaze by default). | ||
The Spark plan is completely free and has all features needed for running this project. | ||
Congratulations! Your project should be available at: https://[your-project].web.app | ||
If it's not, check if the build finished successfully: https://console.cloud.google.com/cloud-build/builds?project=[your-project] | ||
|
||
If you need help, feel free to contact us at https://threedots.tech | ||
``` | ||
|
||
### Screenshots | ||
|
||
![Wild Workouts login](https://threedots.tech/media/serverless-cloud-run-firebase-modern-go-app/login.png "Logo Title Text 1") | ||
![Wild Workouts trainer's schedule](https://threedots.tech/media/serverless-cloud-run-firebase-modern-go-app/schedule.png "Logo Title Text 1") | ||
![Wild Workouts schedule training](https://threedots.tech/media/serverless-cloud-run-firebase-modern-go-app/new-training.png "Logo Title Text 1") |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
// package wild_workouts is an introductory repository that provides | ||
// examples of working with the clean architecture in go. | ||
// | ||
// Concerns: | ||
// * | ||
// | ||
// Praises: | ||
// * | ||
|
||
package wild_workouts |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,84 @@ | ||
package auth | ||
|
||
import ( | ||
"context" | ||
"net/http" | ||
"strings" | ||
|
||
"firebase.google.com/go/v4/auth" | ||
commonerrors "github.com/ThreeDotsLabs/wild-workouts-go-ddd-example/internal/common/errors" | ||
"github.com/ThreeDotsLabs/wild-workouts-go-ddd-example/internal/common/server/httperr" | ||
) | ||
|
||
type FirebaseHttpMiddleware struct { | ||
AuthClient *auth.Client | ||
} | ||
|
||
func (a FirebaseHttpMiddleware) Middleware(next http.Handler) http.Handler { | ||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { | ||
ctx := r.Context() | ||
|
||
bearerToken := a.tokenFromHeader(r) | ||
if bearerToken == "" { | ||
httperr.Unauthorised("empty-bearer-token", nil, w, r) | ||
return | ||
} | ||
|
||
token, err := a.AuthClient.VerifyIDToken(ctx, bearerToken) | ||
if err != nil { | ||
httperr.Unauthorised("unable-to-verify-jwt", err, w, r) | ||
return | ||
} | ||
|
||
// it's always a good idea to use custom type as context value (in this case ctxKey) | ||
// because nobody from the outside of the package will be able to override/read this value | ||
ctx = context.WithValue(ctx, userContextKey, User{ | ||
UUID: token.UID, | ||
Email: token.Claims["email"].(string), | ||
Role: token.Claims["role"].(string), | ||
DisplayName: token.Claims["name"].(string), | ||
}) | ||
r = r.WithContext(ctx) | ||
|
||
next.ServeHTTP(w, r) | ||
}) | ||
} | ||
|
||
func (a FirebaseHttpMiddleware) tokenFromHeader(r *http.Request) string { | ||
headerValue := r.Header.Get("Authorization") | ||
|
||
if len(headerValue) > 7 && strings.ToLower(headerValue[0:6]) == "bearer" { | ||
return headerValue[7:] | ||
} | ||
|
||
return "" | ||
} | ||
|
||
type User struct { | ||
UUID string | ||
Email string | ||
Role string | ||
|
||
DisplayName string | ||
} | ||
|
||
type ctxKey int | ||
|
||
const ( | ||
userContextKey ctxKey = iota | ||
) | ||
|
||
var ( | ||
// if we expect that the user of the function may be interested with concrete error, | ||
// it's a good idea to provide variable with this error | ||
NoUserInContextError = commonerrors.NewAuthorizationError("no user in context", "no-user-found") | ||
) | ||
|
||
func UserFromCtx(ctx context.Context) (User, error) { | ||
u, ok := ctx.Value(userContextKey).(User) | ||
if ok { | ||
return u, nil | ||
} | ||
|
||
return User{}, NoUserInContextError | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,44 @@ | ||
package auth | ||
|
||
import ( | ||
"context" | ||
"net/http" | ||
|
||
"github.com/ThreeDotsLabs/wild-workouts-go-ddd-example/internal/common/server/httperr" | ||
"github.com/dgrijalva/jwt-go" | ||
"github.com/dgrijalva/jwt-go/request" | ||
) | ||
|
||
// HttpMockMiddleware is used in the local environment (which doesn't depend on Firebase) | ||
func HttpMockMiddleware(next http.Handler) http.Handler { | ||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { | ||
var claims jwt.MapClaims | ||
token, err := request.ParseFromRequest( | ||
r, | ||
request.AuthorizationHeaderExtractor, | ||
func(token *jwt.Token) (i interface{}, e error) { | ||
return []byte("mock_secret"), nil | ||
}, | ||
request.WithClaims(&claims), | ||
) | ||
if err != nil { | ||
httperr.BadRequest("unable-to-get-jwt", err, w, r) | ||
return | ||
} | ||
|
||
if !token.Valid { | ||
httperr.BadRequest("invalid-jwt", nil, w, r) | ||
return | ||
} | ||
|
||
ctx := context.WithValue(r.Context(), userContextKey, User{ | ||
UUID: claims["user_uuid"].(string), | ||
Email: claims["email"].(string), | ||
Role: claims["role"].(string), | ||
DisplayName: claims["name"].(string), | ||
}) | ||
r = r.WithContext(ctx) | ||
|
||
next.ServeHTTP(w, r) | ||
}) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
package client | ||
|
||
import ( | ||
"context" | ||
"fmt" | ||
"strings" | ||
|
||
"cloud.google.com/go/compute/metadata" | ||
"github.com/pkg/errors" | ||
"google.golang.org/grpc/credentials" | ||
) | ||
|
||
type metadataServerToken struct { | ||
serviceURL string | ||
} | ||
|
||
func newMetadataServerToken(grpcAddr string) credentials.PerRPCCredentials { | ||
// based on https://cloud.google.com/run/docs/authenticating/service-to-service#go | ||
// service need to have https prefix without port | ||
serviceURL := "https://" + strings.Split(grpcAddr, ":")[0] | ||
|
||
return metadataServerToken{serviceURL} | ||
} | ||
|
||
// GetRequestMetadata is called on every request, so we are sure that token is always not expired | ||
func (t metadataServerToken) GetRequestMetadata(ctx context.Context, in ...string) (map[string]string, error) { | ||
// based on https://cloud.google.com/run/docs/authenticating/service-to-service#go | ||
tokenURL := fmt.Sprintf("/instance/service-accounts/default/identity?audience=%s", t.serviceURL) | ||
idToken, err := metadata.Get(tokenURL) | ||
if err != nil { | ||
return nil, errors.Wrap(err, "cannot query id token for gRPC") | ||
} | ||
|
||
return map[string]string{ | ||
"authorization": "Bearer " + idToken, | ||
}, nil | ||
} | ||
|
||
func (metadataServerToken) RequireTransportSecurity() bool { | ||
return true | ||
} |
Oops, something went wrong.