# Code convention
here we define our `subjective` best practices in how to code in this service.
Values we want to achive:
1. Development experience
2. DRY code when possible, but not always
3. Modularity
4. working code
## code structure
We tries to use the [new design pattern](https://gitlab.com/ruangguru/source/-/tree/master/example-user-api), but currently still migrating from the old stuff
taken from readme
```
.
├── client
│ ├── api
│ ├── googleapis
│ ├── user-token-api
│ ├── dto.go
│ ├── client.go
│ ├── grpc
│ ├── rg-notification # generated from dependency (contract framework)
├── config # service envar config
│ ├── config.go
├── core # core logic will be in this folder
│ ├── entity # encapsulate enterprise wide rule
│ ├── user.go
│ ├── usertoken.go
│ ├── entity1.go
│ ├── entity2.go
│ ├── module # usecase/service layer
│ ├── auth-usecase # usecase layer to handle user journey in authentication
│ ├── profile-usecase # usecase layer to handle user journey in profile page / CMS
│ ├── module-service # best practice using usecase, but we can use service if it's a simple service
│ ├── repository # interface adaptors for usecase to communicate with outermost communication
│ ├── user-repo # interface for user domain repository
│ ├── token-repo
├── handler # An input port (driving port) lets the application core to expose the functionality to the outside of
# the world. Handler will communicate to module (usecase layer) to serve the purpose
│ ├── api
│ ├── grpc
│ ├── user # code generated (contract framework: *pb.go, *.pb.morse.go, etc.)
│ ├── handler.go # Implement the grpc/rest api handler
│ ├── subscriber
│ ├── handler.go # Implement the subscriber handler
│ ├── worker
│ ├── handler.go # Implement the worker handler
├── migrations # rg-osc migrations file will be here
│ ├── ...
├── repository
├── pkg # Supporting library / script will be written here
│ ├── helpers
│ ├── password
├── go.mod
├── go.sum
├── main.go
├── service.yaml
└── README.md
```
conventions:
1. Function mapping from handler to usecases is 1to1
2. Entity can contains business logic
3. Focus on the developers experience
## error handling
0. starts error message in lowercase
1. handler matching error uses `errors.Is`
2. error yang di match defined di `./core/module/errors.go`
3. `./repository/*` should not define any error
4. `./repository/*` uses errors from `./core/repo/<repo>.go`
5. return error from handler uses `fmt.Errorf("%w: %v", <module/errors>, returned error)`
6. return error from repository uses `fmt.Errorf("%w: %v", <repo/errors>, returned error)`
7. handler maps all errors from repository to `core/module/errors.go`
### how define the errors
``` go
var (
ErrInternal = errors.New("error Internal Error")
ErrUpstream = errors.New("error Internal Service Call Error")
ErrDBError = errors.New("error DB Error")
ErrBadRequest = errors.New("error Bad Request")
ErrWebsocket
ErrUserBanned = fmt.Errorf("%w: error Forbidden User Banned", ErrBadRequest)
ErrUserNotMT = fmt.Errorf("%w: error Forbidden, not MT", ErrBadRequest)
ErrThreadContentTooShort = fmt.Errorf("%w: error thread content too short", ErrInternal)
ErrUnregisteredClient = fmt.Errorf("%w: unregistered source client", ErrBadRequest)
ErrInvalidSourceName = fmt.Errorf("%w: invalid source name", ErrBadRequest)
ErrEmptyGroupSerial = fmt.Errorf("%w: groupSerial must not be empty", ErrBadRequest)
ErrQuestionNotFound = fmt.Errorf("%w: question not found", ErrBadRequest)
ErrQuestionNotReported = fmt.Errorf("%w: question not reported", ErrBadRequest)
ErrUserBannedError = fmt.Errorf("%w: error Forbidden User Banned", ErrBadRequest)
```
``` go
--- handler
var threadErrorMap = {
[module.BadRequest]: pb.CreateForum__BAD_REQUEST.WithMessage,
[module.InternalErr]: pb.CreateForum__INTERNAL_ERR.WithInternal,
[module.NotFound]: pb.CreateForum__NOT_FOUND,
[module.ErrInternal]: pb.CreateForum__INTERNAL_ERR,
}
func (threadError) showMsg(err) {
return threadErrorMap[err].WithMessage(err)
}
showMsg(ErrUnregisteredClient)
switch err {
case BadRequest:
return Bad....
case BadRequest:
return Bad....
case BadRequest:
return Bad....
case BadRequest:
return Bad....
}
if errors.Is(err, BadRequest) {
return pb.CreateForum__BAD_REQUEST.WithMessage
} else if errors.Is(err, InternalErr) {
return pb.CreateForum__INTERNAL_ERR.WithInternal
} else if errors.Is(err, NotFound) {
return pb.CreateForum__NOT_FOUND
} else if errors.Is(err, InternalErr) {
return pb.CreateForum__INTERNAL_ERR
}
-- how to return
return threadError{}.showMsg(err)
type Errors interface {
WithMessage(msg string)
WithMessageInternal(msg string)
c
map[string]Errors
return pb.CreateForumThreadError__INTERNAL_ERROR.WithMessage(err.Error()).WithMessageInternal(err.Error())
```
## config handling
we can do these stuff but we don't want to, yet.
TODO: add the `stuff`
## variable naming
1. serializer/deserializer/transform functions uses prefix `To` and suffix `Request` or `Response `
2. use domain (not to be confused with business domain) as a prefix for public packages/variables
3. domains prefix meanings:
- Err: error
- uc: usecases
- client: service or client
- don't use `cli` as prefix
- repo: repository
4. use prefix `is` for boolean vars
5. map vars uses format `<Value>By<Keys>` or `<Keys><Value>Mapping` (in review, left form is preferrable)
6. array variables uses plural form in the most left noun
correct exaples
```go
type QuestionAttachments []QuestionAttachment
serials = "a,b,c" // this is wrong
serialsJoined = "a,b,c" // this is ok
[]serials = [a, b, c] // this is ok
```
## migration
## how to debug
## how to monitor & alert
## tools
### kubectl
use this alias in .zshrc or .bashrc
```bash
alias kcrg='kubectl --context gke_silicon-airlock-153323_asia-southeast1-a_ruangguru-k8s'
alias kcgl='kubectl --context gke_silicon-airlock-153323_asia-southeast1_ase1-glo-infra-1'
alias kcid='kubectl --context gke_silicon-airlock-153323_asia-southeast1_ase1-id-prod-1'
alias kcth='kubectl --context gke_silicon-airlock-153323_asia-southeast1_ase1-th-prod-1'
alias kcvn='kubectl --context gke_silicon-airlock-153323_asia-southeast1_ase1-vn-prod-1'
```
get list of pods
```bash
kcrg|kcid get pods --all-namespaces -lapp=<service-name>-<role>
```
get logs
```bash
kcrg logs <pod-name> -n <namespace> -c <service-name> [-f]
```
get logs from all pods
``` bash
kcid -n production logs deployment/rubel-forum --all-containers=true --since=1m
```
exec command on pods
``` bash
kcrg exec rg-forum-8b7bd76cd-rvvfb -n production --tty --stdin -- /bin/bash
```
run cf local
```bash
node node_modules/@google-cloud/functions-framework --target=modularComponents
```