Compare commits
1 Commits
main
...
dda5060474
| Author | SHA1 | Date | |
|---|---|---|---|
| dda5060474 |
@@ -1,64 +1,14 @@
|
|||||||
# Git files
|
# Docker ignore file for MkDocs build
|
||||||
.git
|
site/
|
||||||
|
.mkdocs_cache/
|
||||||
|
__pycache__/
|
||||||
|
*.pyc
|
||||||
|
*.pyo
|
||||||
|
*.pyd
|
||||||
|
.Python
|
||||||
|
venv/
|
||||||
|
env/
|
||||||
|
ENV/
|
||||||
|
.git/
|
||||||
.gitignore
|
.gitignore
|
||||||
.gitattributes
|
|
||||||
|
|
||||||
# Documentation
|
|
||||||
docs/
|
|
||||||
*.md
|
|
||||||
!README.md
|
|
||||||
|
|
||||||
# Development files
|
|
||||||
.envrc
|
|
||||||
shell.nix
|
|
||||||
.direnv/
|
|
||||||
|
|
||||||
# Build artifacts
|
|
||||||
bin/
|
|
||||||
*.exe
|
|
||||||
*.exe~
|
|
||||||
*.dll
|
|
||||||
*.so
|
|
||||||
*.dylib
|
|
||||||
*.test
|
|
||||||
*.out
|
|
||||||
auth-service
|
|
||||||
identity-service
|
|
||||||
authz-service
|
|
||||||
audit-service
|
|
||||||
platform
|
|
||||||
api-gateway
|
|
||||||
|
|
||||||
# Test files
|
|
||||||
*_test.go
|
|
||||||
test/
|
|
||||||
*.test
|
|
||||||
|
|
||||||
# IDE files
|
|
||||||
.vscode/
|
|
||||||
.idea/
|
|
||||||
*.swp
|
|
||||||
*.swo
|
|
||||||
*~
|
|
||||||
.DS_Store
|
|
||||||
|
|
||||||
# Logs
|
|
||||||
*.log
|
|
||||||
|
|
||||||
# Temporary files
|
|
||||||
tmp/
|
|
||||||
temp/
|
|
||||||
*.tmp
|
|
||||||
|
|
||||||
# Docker files (don't copy into Docker)
|
|
||||||
docker-compose*.yml
|
|
||||||
Dockerfile*
|
|
||||||
|
|
||||||
# CI/CD
|
|
||||||
.github/
|
|
||||||
.gitlab-ci.yml
|
|
||||||
.circleci/
|
|
||||||
|
|
||||||
# Coverage
|
|
||||||
coverage.out
|
|
||||||
coverage.html
|
|
||||||
|
|||||||
5
.envrc
5
.envrc
@@ -1,5 +0,0 @@
|
|||||||
# Automatically load nix-shell when entering this directory
|
|
||||||
# Requires direnv: https://direnv.net/
|
|
||||||
# Run: direnv allow
|
|
||||||
use nix
|
|
||||||
|
|
||||||
123
.github/workflows/ci.yml
vendored
123
.github/workflows/ci.yml
vendored
@@ -17,7 +17,7 @@ jobs:
|
|||||||
- name: Set up Go
|
- name: Set up Go
|
||||||
uses: actions/setup-go@v5
|
uses: actions/setup-go@v5
|
||||||
with:
|
with:
|
||||||
go-version: '1.25.3'
|
go-version: '1.24'
|
||||||
|
|
||||||
- name: Cache Go modules
|
- name: Cache Go modules
|
||||||
uses: actions/cache@v4
|
uses: actions/cache@v4
|
||||||
@@ -33,32 +33,6 @@ jobs:
|
|||||||
- name: Verify dependencies
|
- name: Verify dependencies
|
||||||
run: go mod verify
|
run: go mod verify
|
||||||
|
|
||||||
- name: Install protoc and plugins
|
|
||||||
run: |
|
|
||||||
apk add --no-cache protobuf-dev protoc
|
|
||||||
go install google.golang.org/protobuf/cmd/protoc-gen-go@latest
|
|
||||||
go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest
|
|
||||||
echo "$HOME/go/bin" >> $GITHUB_PATH
|
|
||||||
|
|
||||||
- name: Generate code
|
|
||||||
run: |
|
|
||||||
make generate-proto
|
|
||||||
echo "Checking for Ent schema directory..."
|
|
||||||
if [ -d "ent/schema" ]; then
|
|
||||||
echo "Generating Ent code..."
|
|
||||||
go install entgo.io/ent/cmd/ent@latest
|
|
||||||
cd ent/schema && go run -mod=mod entgo.io/ent/cmd/ent generate .
|
|
||||||
echo "Copying Ent code to internal/ent..."
|
|
||||||
cd .. && mkdir -p ../internal/ent
|
|
||||||
cp -r *.go */ ../internal/ent/ 2>/dev/null || true
|
|
||||||
rm -f ../internal/ent/generate.go
|
|
||||||
rm -rf ../internal/ent/schema
|
|
||||||
echo "Verifying internal/ent/ent.go exists..."
|
|
||||||
ls -la ../internal/ent/ent.go || echo "ERROR: ent.go not found!"
|
|
||||||
else
|
|
||||||
echo "WARNING: ent/schema directory not found!"
|
|
||||||
fi
|
|
||||||
|
|
||||||
- name: Check for test files
|
- name: Check for test files
|
||||||
id: check-tests
|
id: check-tests
|
||||||
run: |
|
run: |
|
||||||
@@ -77,7 +51,7 @@ jobs:
|
|||||||
if: steps.check-tests.outputs.tests_exist == 'true'
|
if: steps.check-tests.outputs.tests_exist == 'true'
|
||||||
env:
|
env:
|
||||||
CGO_ENABLED: 1
|
CGO_ENABLED: 1
|
||||||
run: make test-coverage
|
run: go test -v -race -coverprofile=coverage.out -timeout=5m ./...
|
||||||
|
|
||||||
- name: Upload coverage
|
- name: Upload coverage
|
||||||
if: steps.check-tests.outputs.tests_exist == 'true'
|
if: steps.check-tests.outputs.tests_exist == 'true'
|
||||||
@@ -88,7 +62,9 @@ jobs:
|
|||||||
|
|
||||||
- name: Verify build (no tests)
|
- name: Verify build (no tests)
|
||||||
if: steps.check-tests.outputs.tests_exist == 'false'
|
if: steps.check-tests.outputs.tests_exist == 'false'
|
||||||
run: make build
|
run: |
|
||||||
|
echo "No tests found. Verifying code compiles instead..."
|
||||||
|
go build ./...
|
||||||
|
|
||||||
lint:
|
lint:
|
||||||
name: Lint
|
name: Lint
|
||||||
@@ -100,45 +76,15 @@ jobs:
|
|||||||
- name: Set up Go
|
- name: Set up Go
|
||||||
uses: actions/setup-go@v5
|
uses: actions/setup-go@v5
|
||||||
with:
|
with:
|
||||||
go-version: '1.25.3'
|
go-version: '1.24'
|
||||||
|
|
||||||
- name: Download dependencies
|
- name: Install golangci-lint v2.1.6
|
||||||
run: go mod download
|
|
||||||
|
|
||||||
- name: Install protoc and plugins
|
|
||||||
run: |
|
run: |
|
||||||
apk add --no-cache protobuf-dev protoc
|
curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(go env GOPATH)/bin v2.1.6
|
||||||
go install google.golang.org/protobuf/cmd/protoc-gen-go@latest
|
|
||||||
go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest
|
|
||||||
echo "$HOME/go/bin" >> $GITHUB_PATH
|
|
||||||
|
|
||||||
- name: Generate code
|
|
||||||
run: |
|
|
||||||
make generate-proto
|
|
||||||
echo "Checking for Ent schema directory..."
|
|
||||||
if [ -d "ent/schema" ]; then
|
|
||||||
echo "Generating Ent code..."
|
|
||||||
go install entgo.io/ent/cmd/ent@latest
|
|
||||||
cd ent/schema && go run -mod=mod entgo.io/ent/cmd/ent generate .
|
|
||||||
echo "Copying Ent code to internal/ent..."
|
|
||||||
cd .. && mkdir -p ../internal/ent
|
|
||||||
cp -r *.go */ ../internal/ent/ 2>/dev/null || true
|
|
||||||
rm -f ../internal/ent/generate.go
|
|
||||||
rm -rf ../internal/ent/schema
|
|
||||||
echo "Verifying internal/ent/ent.go exists..."
|
|
||||||
ls -la ../internal/ent/ent.go || echo "ERROR: ent.go not found!"
|
|
||||||
else
|
|
||||||
echo "WARNING: ent/schema directory not found!"
|
|
||||||
fi
|
|
||||||
|
|
||||||
- name: Install golangci-lint
|
|
||||||
run: |
|
|
||||||
curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(go env GOPATH)/bin
|
|
||||||
echo "$(go env GOPATH)/bin" >> $GITHUB_PATH
|
echo "$(go env GOPATH)/bin" >> $GITHUB_PATH
|
||||||
golangci-lint --version
|
|
||||||
|
|
||||||
- name: Run linters
|
- name: Run golangci-lint
|
||||||
run: make lint
|
run: golangci-lint run --timeout=5m
|
||||||
|
|
||||||
build:
|
build:
|
||||||
name: Build
|
name: Build
|
||||||
@@ -150,7 +96,7 @@ jobs:
|
|||||||
- name: Set up Go
|
- name: Set up Go
|
||||||
uses: actions/setup-go@v5
|
uses: actions/setup-go@v5
|
||||||
with:
|
with:
|
||||||
go-version: '1.25.3'
|
go-version: '1.24'
|
||||||
|
|
||||||
- name: Cache Go modules
|
- name: Cache Go modules
|
||||||
uses: actions/cache@v4
|
uses: actions/cache@v4
|
||||||
@@ -163,46 +109,14 @@ jobs:
|
|||||||
- name: Download dependencies
|
- name: Download dependencies
|
||||||
run: go mod download
|
run: go mod download
|
||||||
|
|
||||||
- name: Install protoc and plugins
|
|
||||||
run: |
|
|
||||||
apk add --no-cache protobuf-dev protoc
|
|
||||||
go install google.golang.org/protobuf/cmd/protoc-gen-go@latest
|
|
||||||
go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest
|
|
||||||
echo "$HOME/go/bin" >> $GITHUB_PATH
|
|
||||||
|
|
||||||
- name: Generate code
|
|
||||||
run: |
|
|
||||||
make generate-proto
|
|
||||||
echo "Checking for Ent schema directory..."
|
|
||||||
if [ -d "ent/schema" ]; then
|
|
||||||
echo "Generating Ent code..."
|
|
||||||
go install entgo.io/ent/cmd/ent@latest
|
|
||||||
cd ent/schema && go run -mod=mod entgo.io/ent/cmd/ent generate .
|
|
||||||
echo "Copying Ent code to internal/ent..."
|
|
||||||
cd .. && mkdir -p ../internal/ent
|
|
||||||
cp -r *.go */ ../internal/ent/ 2>/dev/null || true
|
|
||||||
rm -f ../internal/ent/generate.go
|
|
||||||
rm -rf ../internal/ent/schema
|
|
||||||
echo "Verifying internal/ent/ent.go exists..."
|
|
||||||
ls -la ../internal/ent/ent.go || echo "ERROR: ent.go not found!"
|
|
||||||
else
|
|
||||||
echo "WARNING: ent/schema directory not found!"
|
|
||||||
fi
|
|
||||||
|
|
||||||
- name: Build
|
- name: Build
|
||||||
run: make build
|
run: go build -v -o bin/platform ./cmd/platform
|
||||||
|
|
||||||
- name: Upload build artifacts
|
- name: Upload build artifacts
|
||||||
uses: actions/upload-artifact@v3
|
uses: actions/upload-artifact@v3
|
||||||
with:
|
with:
|
||||||
name: binaries
|
name: platform-binary
|
||||||
path: |
|
path: bin/platform
|
||||||
bin/platform
|
|
||||||
bin/api-gateway
|
|
||||||
bin/auth-service
|
|
||||||
bin/identity-service
|
|
||||||
bin/authz-service
|
|
||||||
bin/audit-service
|
|
||||||
retention-days: 7
|
retention-days: 7
|
||||||
|
|
||||||
fmt:
|
fmt:
|
||||||
@@ -215,7 +129,12 @@ jobs:
|
|||||||
- name: Set up Go
|
- name: Set up Go
|
||||||
uses: actions/setup-go@v5
|
uses: actions/setup-go@v5
|
||||||
with:
|
with:
|
||||||
go-version: '1.25.3'
|
go-version: '1.24'
|
||||||
|
|
||||||
- name: Check formatting
|
- name: Check formatting
|
||||||
run: make fmt-check
|
run: |
|
||||||
|
if [ "$(gofmt -s -l . | wc -l)" -gt 0 ]; then
|
||||||
|
echo "The following files need formatting:"
|
||||||
|
gofmt -s -d .
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|||||||
21
.gitignore
vendored
21
.gitignore
vendored
@@ -7,11 +7,6 @@
|
|||||||
bin/
|
bin/
|
||||||
dist/
|
dist/
|
||||||
platform
|
platform
|
||||||
/api-gateway
|
|
||||||
/audit-service
|
|
||||||
/identity-service
|
|
||||||
/auth-service
|
|
||||||
/authz-service
|
|
||||||
|
|
||||||
# Test binary, built with `go test -c`
|
# Test binary, built with `go test -c`
|
||||||
*.test
|
*.test
|
||||||
@@ -60,18 +55,8 @@ temp/
|
|||||||
docs/site/
|
docs/site/
|
||||||
docs/.mkdocs_cache/
|
docs/.mkdocs_cache/
|
||||||
|
|
||||||
|
# Docker
|
||||||
|
.dockerignore
|
||||||
|
|
||||||
# OS-specific
|
# OS-specific
|
||||||
Thumbs.db
|
Thumbs.db
|
||||||
|
|
||||||
.direnv/
|
|
||||||
|
|
||||||
# Generated protobuf files
|
|
||||||
api/proto/generated/
|
|
||||||
|
|
||||||
# Generated Ent ORM files (but keep schema source files)
|
|
||||||
internal/ent/
|
|
||||||
ent/*.go
|
|
||||||
ent/*/
|
|
||||||
!ent/schema/
|
|
||||||
git.dcentral.systems/
|
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
# golangci-lint configuration
|
# golangci-lint configuration
|
||||||
# See https://golangci-lint.run/usage/configuration/
|
# See https://golangci-lint.run/usage/configuration/
|
||||||
|
|
||||||
version: "2"
|
version: 2
|
||||||
|
|
||||||
run:
|
run:
|
||||||
timeout: 5m
|
timeout: 5m
|
||||||
@@ -13,12 +13,41 @@ linters:
|
|||||||
- errcheck
|
- errcheck
|
||||||
- govet
|
- govet
|
||||||
- staticcheck
|
- staticcheck
|
||||||
|
- revive
|
||||||
- gosec
|
- gosec
|
||||||
disable:
|
disable:
|
||||||
- gocritic # Can be enabled later for stricter checks
|
- gocritic # Can be enabled later for stricter checks
|
||||||
|
|
||||||
|
linters-settings:
|
||||||
|
revive:
|
||||||
|
rules:
|
||||||
|
- name: exported
|
||||||
|
severity: warning
|
||||||
|
arguments:
|
||||||
|
- checkPrivateReceivers
|
||||||
|
# Disable stuttering check - interface names like ConfigProvider are acceptable
|
||||||
|
- name: package-comments
|
||||||
|
severity: warning
|
||||||
|
gosec:
|
||||||
|
severity: medium
|
||||||
|
errcheck:
|
||||||
|
check-blank: true
|
||||||
|
|
||||||
issues:
|
issues:
|
||||||
|
exclude-use-default: false
|
||||||
max-issues-per-linter: 0
|
max-issues-per-linter: 0
|
||||||
max-same-issues: 0
|
max-same-issues: 0
|
||||||
# Note: exclusion rules moved in v2 config; keep minimal allowed keys
|
exclude-rules:
|
||||||
|
# Exclude test files from some checks
|
||||||
|
- path: _test\.go
|
||||||
|
linters:
|
||||||
|
- errcheck
|
||||||
|
- gosec
|
||||||
|
# ConfigProvider stuttering is acceptable - it's a common pattern for interfaces
|
||||||
|
- path: pkg/config/config\.go
|
||||||
|
linters:
|
||||||
|
- revive
|
||||||
|
|
||||||
|
output:
|
||||||
|
print-issued-lines: true
|
||||||
|
print-linter-name: true
|
||||||
|
|||||||
24
AGENTS.md
24
AGENTS.md
@@ -150,10 +150,8 @@ When working on this project, follow this workflow:
|
|||||||
- Use descriptive branch names (e.g., `feature/epic1-http-server`, `bugfix/auth-token-expiry`, `enhancement/rate-limiting`)
|
- Use descriptive branch names (e.g., `feature/epic1-http-server`, `bugfix/auth-token-expiry`, `enhancement/rate-limiting`)
|
||||||
- Branch names should follow the pattern: `{type}/{epic}-{short-description}` or `{type}/{story-id}-{short-description}`
|
- Branch names should follow the pattern: `{type}/{epic}-{short-description}` or `{type}/{story-id}-{short-description}`
|
||||||
- **ALWAYS create a commit** after successfully implementing a feature that:
|
- **ALWAYS create a commit** after successfully implementing a feature that:
|
||||||
- ✅ Builds successfully (`make build` passes)
|
- ✅ Builds successfully (`go build` passes)
|
||||||
- ✅ Tests pass (`make test` passes)
|
- ✅ Tests pass (`go test` passes)
|
||||||
- ✅ Lint pass (`make lint` passes)
|
|
||||||
- ✅ fmt-check pass (`make fmt-check` passes)
|
|
||||||
- ✅ Meets all acceptance criteria from the story
|
- ✅ Meets all acceptance criteria from the story
|
||||||
- Commit messages should be clear and descriptive, referencing the story/epic when applicable
|
- Commit messages should be clear and descriptive, referencing the story/epic when applicable
|
||||||
- Never commit directly to `main` branch
|
- Never commit directly to `main` branch
|
||||||
@@ -186,10 +184,9 @@ When working on this project, follow this workflow:
|
|||||||
- Meet the acceptance criteria
|
- Meet the acceptance criteria
|
||||||
- Use the implementation notes as guidance
|
- Use the implementation notes as guidance
|
||||||
- Follow the patterns established in `playbook.md`
|
- Follow the patterns established in `playbook.md`
|
||||||
- Implement tests
|
|
||||||
|
|
||||||
### 6. Verify Alignment
|
### 6. Verify Alignment
|
||||||
- Ensure code follows Hexagonal Architecture principles
|
- Ensure code follows Clean/Hexagonal Architecture principles
|
||||||
- Verify it aligns with microservices architecture
|
- Verify it aligns with microservices architecture
|
||||||
- Check that it follows plugin-first design
|
- Check that it follows plugin-first design
|
||||||
- Confirm security-by-design principles are followed
|
- Confirm security-by-design principles are followed
|
||||||
@@ -197,14 +194,8 @@ When working on this project, follow this workflow:
|
|||||||
|
|
||||||
### 7. Commit Changes
|
### 7. Commit Changes
|
||||||
- **ALWAYS commit** after successful implementation
|
- **ALWAYS commit** after successful implementation
|
||||||
- Verify that everything is in order before commit:
|
- Ensure the code builds (`go build`)
|
||||||
- there is a Gitea Runner image in ci/pre-commit
|
- Ensure all tests pass (`go test`)
|
||||||
- run scripts/pre-commit-check.sh
|
|
||||||
- Ensure the code builds (`make build`)
|
|
||||||
- Ensure all tests pass (`make test`)
|
|
||||||
- Ensure there are no linter issues (`make lint`)
|
|
||||||
- Ensure there are no fmt issues (`make fmt-check`)
|
|
||||||
- If there are issues, fix them before comitting
|
|
||||||
- Verify all acceptance criteria are met
|
- Verify all acceptance criteria are met
|
||||||
- Write a clear, descriptive commit message
|
- Write a clear, descriptive commit message
|
||||||
|
|
||||||
@@ -214,7 +205,7 @@ When working on this project, follow this workflow:
|
|||||||
|
|
||||||
The project follows these core principles (documented in `requirements.md` and `playbook.md`):
|
The project follows these core principles (documented in `requirements.md` and `playbook.md`):
|
||||||
|
|
||||||
1. **Hexagonal Architecture**
|
1. **Clean/Hexagonal Architecture**
|
||||||
- Clear separation between `pkg/` (interfaces) and `internal/` (implementations)
|
- Clear separation between `pkg/` (interfaces) and `internal/` (implementations)
|
||||||
- Domain code in `internal/domain`
|
- Domain code in `internal/domain`
|
||||||
- Only interfaces exported from `pkg/`
|
- Only interfaces exported from `pkg/`
|
||||||
@@ -276,7 +267,7 @@ After implementing a feature, verify:
|
|||||||
2. **Committing without verification** - Never commit code that doesn't build, has failing tests, or doesn't meet acceptance criteria
|
2. **Committing without verification** - Never commit code that doesn't build, has failing tests, or doesn't meet acceptance criteria
|
||||||
3. **Implementing without checking stories** - Stories contain specific deliverables and acceptance criteria
|
3. **Implementing without checking stories** - Stories contain specific deliverables and acceptance criteria
|
||||||
4. **Ignoring ADRs** - ADRs document why decisions were made; don't reinvent the wheel
|
4. **Ignoring ADRs** - ADRs document why decisions were made; don't reinvent the wheel
|
||||||
5. **Violating architecture principles** - Code must follow Hexagonal Architecture
|
5. **Violating architecture principles** - Code must follow Clean/Hexagonal Architecture
|
||||||
6. **Missing acceptance criteria** - All stories have specific criteria that must be met
|
6. **Missing acceptance criteria** - All stories have specific criteria that must be met
|
||||||
7. **Not following module patterns** - Modules must implement the `IModule` interface correctly
|
7. **Not following module patterns** - Modules must implement the `IModule` interface correctly
|
||||||
8. **Skipping observability** - All features must include proper logging, metrics, and tracing
|
8. **Skipping observability** - All features must include proper logging, metrics, and tracing
|
||||||
@@ -310,7 +301,6 @@ If you make architectural decisions or significant changes:
|
|||||||
2. Update architecture documents if structure changes
|
2. Update architecture documents if structure changes
|
||||||
3. Update stories if implementation details change
|
3. Update stories if implementation details change
|
||||||
4. Keep documentation in sync with code
|
4. Keep documentation in sync with code
|
||||||
5. Do not use any emojis
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
55
Makefile
55
Makefile
@@ -18,7 +18,7 @@ help:
|
|||||||
@echo " make lint - Run linters"
|
@echo " make lint - Run linters"
|
||||||
@echo " make fmt - Format code"
|
@echo " make fmt - Format code"
|
||||||
@echo " make fmt-check - Check code formatting"
|
@echo " make fmt-check - Check code formatting"
|
||||||
@echo " make build - Build all service binaries"
|
@echo " make build - Build platform binary"
|
||||||
@echo " make clean - Clean build artifacts"
|
@echo " make clean - Clean build artifacts"
|
||||||
@echo " make docker-build - Build Docker image"
|
@echo " make docker-build - Build Docker image"
|
||||||
@echo " make docker-run - Run Docker container"
|
@echo " make docker-run - Run Docker container"
|
||||||
@@ -85,14 +85,9 @@ fmt-check:
|
|||||||
@echo "Code is properly formatted"
|
@echo "Code is properly formatted"
|
||||||
|
|
||||||
build:
|
build:
|
||||||
@echo "Building all service binaries..."
|
@echo "Building platform binary..."
|
||||||
$(GO) build -v -o bin/platform ./cmd/platform
|
$(GO) build -v -o $(BINARY_PATH) ./cmd/platform
|
||||||
$(GO) build -v -o bin/api-gateway ./cmd/api-gateway
|
@echo "Build complete: $(BINARY_PATH)"
|
||||||
$(GO) build -v -o bin/auth-service ./cmd/auth-service
|
|
||||||
$(GO) build -v -o bin/identity-service ./cmd/identity-service
|
|
||||||
$(GO) build -v -o bin/authz-service ./cmd/authz-service
|
|
||||||
$(GO) build -v -o bin/audit-service ./cmd/audit-service
|
|
||||||
@echo "Build complete: bin/platform, bin/api-gateway, bin/auth-service, bin/identity-service, bin/authz-service, bin/audit-service"
|
|
||||||
|
|
||||||
clean:
|
clean:
|
||||||
@echo "Cleaning build artifacts..."
|
@echo "Cleaning build artifacts..."
|
||||||
@@ -115,48 +110,6 @@ generate:
|
|||||||
@echo "Running code generation..."
|
@echo "Running code generation..."
|
||||||
$(GO) generate ./...
|
$(GO) generate ./...
|
||||||
|
|
||||||
generate-proto:
|
|
||||||
@echo "Generating gRPC code from proto files..."
|
|
||||||
@if ! command -v protoc > /dev/null; then \
|
|
||||||
echo "protoc not found. Install Protocol Buffers compiler."; \
|
|
||||||
exit 1; \
|
|
||||||
fi
|
|
||||||
@if ! command -v protoc-gen-go > /dev/null; then \
|
|
||||||
echo "protoc-gen-go not found. Install with: go install google.golang.org/protobuf/cmd/protoc-gen-go@latest"; \
|
|
||||||
exit 1; \
|
|
||||||
fi
|
|
||||||
@if ! command -v protoc-gen-go-grpc > /dev/null; then \
|
|
||||||
echo "protoc-gen-go-grpc not found. Install with: go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest"; \
|
|
||||||
exit 1; \
|
|
||||||
fi
|
|
||||||
@mkdir -p api/proto/generated/audit/v1 api/proto/generated/auth/v1 api/proto/generated/authz/v1 api/proto/generated/identity/v1
|
|
||||||
@protoc --go_out=api/proto/generated --go_opt=paths=source_relative \
|
|
||||||
--go-grpc_out=api/proto/generated --go-grpc_opt=paths=source_relative \
|
|
||||||
--proto_path=api/proto \
|
|
||||||
api/proto/audit.proto
|
|
||||||
@if [ -f api/proto/generated/audit.pb.go ]; then mv api/proto/generated/audit.pb.go api/proto/generated/audit/v1/audit.pb.go; fi
|
|
||||||
@if [ -f api/proto/generated/audit_grpc.pb.go ]; then mv api/proto/generated/audit_grpc.pb.go api/proto/generated/audit/v1/audit_grpc.pb.go; fi
|
|
||||||
@protoc --go_out=api/proto/generated --go_opt=paths=source_relative \
|
|
||||||
--go-grpc_out=api/proto/generated --go-grpc_opt=paths=source_relative \
|
|
||||||
--proto_path=api/proto \
|
|
||||||
api/proto/auth.proto
|
|
||||||
@if [ -f api/proto/generated/auth.pb.go ]; then mv api/proto/generated/auth.pb.go api/proto/generated/auth/v1/auth.pb.go; fi
|
|
||||||
@if [ -f api/proto/generated/auth_grpc.pb.go ]; then mv api/proto/generated/auth_grpc.pb.go api/proto/generated/auth/v1/auth_grpc.pb.go; fi
|
|
||||||
@protoc --go_out=api/proto/generated --go_opt=paths=source_relative \
|
|
||||||
--go-grpc_out=api/proto/generated --go-grpc_opt=paths=source_relative \
|
|
||||||
--proto_path=api/proto \
|
|
||||||
api/proto/authz.proto
|
|
||||||
@if [ -f api/proto/generated/authz.pb.go ]; then mv api/proto/generated/authz.pb.go api/proto/generated/authz/v1/authz.pb.go; fi
|
|
||||||
@if [ -f api/proto/generated/authz_grpc.pb.go ]; then mv api/proto/generated/authz_grpc.pb.go api/proto/generated/authz/v1/authz_grpc.pb.go; fi
|
|
||||||
@protoc --go_out=api/proto/generated --go_opt=paths=source_relative \
|
|
||||||
--go-grpc_out=api/proto/generated --go-grpc_opt=paths=source_relative \
|
|
||||||
--proto_path=api/proto \
|
|
||||||
api/proto/identity.proto
|
|
||||||
@if [ -f api/proto/generated/identity.pb.go ]; then mv api/proto/generated/identity.pb.go api/proto/generated/identity/v1/identity.pb.go; fi
|
|
||||||
@if [ -f api/proto/generated/identity_grpc.pb.go ]; then mv api/proto/generated/identity_grpc.pb.go api/proto/generated/identity/v1/identity_grpc.pb.go; fi
|
|
||||||
@rm -f api/proto/generated/*.pb.go api/proto/generated/*.proto 2>/dev/null || true
|
|
||||||
@echo "gRPC code generation complete"
|
|
||||||
|
|
||||||
verify: fmt-check lint test
|
verify: fmt-check lint test
|
||||||
@echo "Verification complete"
|
@echo "Verification complete"
|
||||||
|
|
||||||
|
|||||||
288
README.md
288
README.md
@@ -1,81 +1,71 @@
|
|||||||
# Go Platform
|
# Go Platform (goplt)
|
||||||
|
|
||||||
**SaaS/Enterprise Platform – Go Edition**
|
**Plugin-friendly SaaS/Enterprise Platform – Go Edition**
|
||||||
|
|
||||||
A modular, extensible platform built with Go that provides a solid foundation for building scalable, secure, and observable applications. The platform supports plugin-based architecture, enabling teams to build feature modules independently while sharing core services.
|
A modular, extensible platform built with Go that provides a solid foundation for building scalable, secure, and observable applications. The platform supports plugin-based architecture, enabling teams to build feature modules independently while sharing core services.
|
||||||
|
|
||||||
## Architecture Overview
|
## 🏗️ Architecture Overview
|
||||||
|
|
||||||
Go Platform follows **Hexagonal Architecture** principles with clear separation between:
|
Go Platform follows **Clean/Hexagonal Architecture** principles with clear separation between:
|
||||||
|
|
||||||
- **Core Kernel**: Foundation services (configuration, logging, observability, dependency injection)
|
- **Core Kernel**: Foundation services (authentication, authorization, configuration, logging, observability)
|
||||||
- **Core Services**: Independent microservices (Auth, Identity, Authz, Audit) with gRPC APIs
|
|
||||||
- **Module Framework**: Plugin system for extending functionality
|
- **Module Framework**: Plugin system for extending functionality
|
||||||
- **Infrastructure Adapters**: Support for databases, caching, event buses, and job scheduling
|
- **Infrastructure Adapters**: Support for databases, caching, event buses, and job scheduling
|
||||||
- **Security-by-Design**: Built-in JWT authentication, RBAC/ABAC authorization, and audit logging
|
- **Security-by-Design**: Built-in JWT authentication, RBAC/ABAC authorization, and audit logging
|
||||||
- **Observability**: OpenTelemetry integration for tracing, metrics, and logging
|
- **Observability**: OpenTelemetry integration for tracing, metrics, and logging
|
||||||
- **Microservices**: Each service is independently deployable with its own database schema
|
|
||||||
|
|
||||||
## Directory Structure
|
### Key Principles
|
||||||
|
|
||||||
|
- **Clean Architecture**: Separation between `pkg/` (interfaces) and `internal/` (implementations)
|
||||||
|
- **Dependency Injection**: Using `uber-go/fx` for lifecycle management
|
||||||
|
- **Microservices-Ready**: Each module is an independent service from day one
|
||||||
|
- **Plugin-First Design**: Extensible architecture supporting static and dynamic module loading
|
||||||
|
- **Security-by-Design**: JWT authentication, RBAC/ABAC, and audit logging
|
||||||
|
- **Observability**: OpenTelemetry, structured logging, and Prometheus metrics
|
||||||
|
|
||||||
|
## 📁 Directory Structure
|
||||||
|
|
||||||
```
|
```
|
||||||
goplt/
|
goplt/
|
||||||
├── cmd/ # Service entry points
|
├── cmd/
|
||||||
│ ├── platform/ # Main platform entry point
|
│ └── platform/ # Main application entry point
|
||||||
│ ├── auth-service/ # Auth Service (JWT authentication)
|
├── internal/ # Private implementation code
|
||||||
│ ├── identity-service/ # Identity Service (user management)
|
│ ├── di/ # Dependency injection container
|
||||||
│ ├── authz-service/ # Authz Service (authorization & RBAC)
|
│ ├── registry/ # Module registry
|
||||||
│ └── audit-service/ # Audit Service (audit logging)
|
│ ├── pluginloader/ # Plugin loader (optional)
|
||||||
├── internal/ # Private implementation code
|
│ ├── config/ # Configuration implementation
|
||||||
│ ├── di/ # Dependency injection container
|
│ ├── logger/ # Logger implementation
|
||||||
│ ├── registry/ # Module registry
|
│ ├── infra/ # Infrastructure adapters
|
||||||
│ ├── pluginloader/ # Plugin loader (optional)
|
│ └── ent/ # Ent ORM schemas
|
||||||
│ ├── config/ # Configuration implementation
|
├── pkg/ # Public interfaces (exported)
|
||||||
│ ├── logger/ # Logger implementation
|
│ ├── config/ # ConfigProvider interface
|
||||||
│ ├── infra/ # Infrastructure adapters
|
│ ├── logger/ # Logger interface
|
||||||
│ ├── ent/ # Ent ORM schemas
|
│ ├── module/ # IModule interface
|
||||||
│ └── client/ # Service clients (gRPC)
|
│ ├── auth/ # Auth interfaces
|
||||||
│ └── grpc/ # gRPC client implementations
|
│ ├── perm/ # Permission DSL
|
||||||
├── pkg/ # Public interfaces (exported)
|
│ └── infra/ # Infrastructure interfaces
|
||||||
│ ├── config/ # ConfigProvider interface
|
├── modules/ # Feature modules
|
||||||
│ ├── logger/ # Logger interface
|
│ └── blog/ # Sample Blog module (Epic 4)
|
||||||
│ ├── module/ # IModule interface
|
├── config/ # Configuration files
|
||||||
│ ├── services/ # Service client interfaces
|
|
||||||
│ ├── auth/ # Auth interfaces
|
|
||||||
│ ├── perm/ # Permission DSL
|
|
||||||
│ └── infra/ # Infrastructure interfaces
|
|
||||||
├── services/ # Service-specific implementations
|
|
||||||
│ └── identity/ # Identity service internals
|
|
||||||
│ └── internal/
|
|
||||||
│ └── password/ # Password hashing (argon2id)
|
|
||||||
├── modules/ # Feature modules
|
|
||||||
│ └── blog/ # Sample Blog module (Epic 4)
|
|
||||||
├── api/ # API definitions
|
|
||||||
│ └── proto/ # gRPC protobuf definitions
|
|
||||||
├── config/ # Configuration files
|
|
||||||
│ ├── default.yaml
|
│ ├── default.yaml
|
||||||
│ ├── development.yaml
|
│ ├── development.yaml
|
||||||
│ └── production.yaml
|
│ └── production.yaml
|
||||||
├── docker-compose.yml # Full deployment (all services)
|
├── api/ # OpenAPI specs
|
||||||
├── docker-compose.dev.yml # Development (infrastructure only)
|
├── scripts/ # Build/test scripts
|
||||||
├── scripts/ # Build/test scripts
|
├── docs/ # Documentation
|
||||||
├── docs/ # Documentation
|
├── ops/ # Operations (Grafana dashboards, etc.)
|
||||||
├── ops/ # Operations (Grafana dashboards, etc.)
|
|
||||||
└── .github/
|
└── .github/
|
||||||
└── workflows/
|
└── workflows/
|
||||||
└── ci.yml
|
└── ci.yml
|
||||||
```
|
```
|
||||||
|
|
||||||
## Quick Start
|
## 🚀 Quick Start
|
||||||
|
|
||||||
### Prerequisites
|
### Prerequisites
|
||||||
|
|
||||||
- **Go 1.25.3+**: [Install Go](https://golang.org/doc/install)
|
- **Go 1.24+**: [Install Go](https://golang.org/doc/install)
|
||||||
- **Make**: For using development commands
|
- **Make**: For using development commands
|
||||||
- **Docker & Docker Compose**: For infrastructure services
|
- **Docker** (optional): For containerized development
|
||||||
- **PostgreSQL 16+**: Database (or use Docker Compose)
|
|
||||||
- **Consul**: Service registry (or use Docker Compose)
|
|
||||||
- **NixOS** (optional): For development environment (`shell.nix` provided)
|
|
||||||
|
|
||||||
### Installation
|
### Installation
|
||||||
|
|
||||||
@@ -85,93 +75,22 @@ goplt/
|
|||||||
cd goplt
|
cd goplt
|
||||||
```
|
```
|
||||||
|
|
||||||
2. **Set up development environment** (NixOS users)
|
2. **Install dependencies**
|
||||||
```bash
|
|
||||||
# If using direnv, it will auto-activate
|
|
||||||
# Otherwise, activate manually:
|
|
||||||
nix-shell
|
|
||||||
```
|
|
||||||
|
|
||||||
3. **Install dependencies**
|
|
||||||
```bash
|
```bash
|
||||||
go mod download
|
go mod download
|
||||||
```
|
```
|
||||||
|
|
||||||
4. **Generate code** (protobuf, Ent schemas)
|
3. **Build the platform**
|
||||||
```bash
|
```bash
|
||||||
make generate-proto
|
make build
|
||||||
make generate-ent
|
|
||||||
```
|
```
|
||||||
|
|
||||||
### Running Services
|
4. **Run the platform**
|
||||||
|
```bash
|
||||||
#### Option 1: Development Mode (Recommended for Development)
|
./bin/platform
|
||||||
|
# Or
|
||||||
Start infrastructure services (PostgreSQL + Consul) in Docker, run services locally:
|
go run cmd/platform/main.go
|
||||||
|
```
|
||||||
```bash
|
|
||||||
# Start infrastructure
|
|
||||||
docker-compose -f docker-compose.dev.yml up -d
|
|
||||||
|
|
||||||
# Verify infrastructure is running
|
|
||||||
docker-compose -f docker-compose.dev.yml ps
|
|
||||||
|
|
||||||
# Start services locally (in separate terminals)
|
|
||||||
go run ./cmd/auth-service/*.go # Port 8081
|
|
||||||
go run ./cmd/identity-service/*.go # Port 8082
|
|
||||||
go run ./cmd/authz-service/*.go # Port 8083
|
|
||||||
go run ./cmd/audit-service/*.go # Port 8084
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Option 2: Full Docker Deployment
|
|
||||||
|
|
||||||
Run everything in Docker:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Build and start all services
|
|
||||||
docker-compose up -d --build
|
|
||||||
|
|
||||||
# View logs
|
|
||||||
docker-compose logs -f
|
|
||||||
|
|
||||||
# Stop all services
|
|
||||||
docker-compose down
|
|
||||||
```
|
|
||||||
|
|
||||||
### Service Endpoints
|
|
||||||
|
|
||||||
Once services are running:
|
|
||||||
|
|
||||||
- **Auth Service**: `localhost:8081` (gRPC)
|
|
||||||
- **Identity Service**: `localhost:8082` (gRPC)
|
|
||||||
- **Authz Service**: `localhost:8083` (gRPC)
|
|
||||||
- **Audit Service**: `localhost:8084` (gRPC)
|
|
||||||
- **Consul UI**: http://localhost:8500/ui
|
|
||||||
- **PostgreSQL**: `localhost:5432`
|
|
||||||
|
|
||||||
### Testing Services
|
|
||||||
|
|
||||||
Use `grpcurl` to test gRPC endpoints:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# List available services
|
|
||||||
grpcurl -plaintext localhost:8081 list
|
|
||||||
|
|
||||||
# Example: Create a user
|
|
||||||
grpcurl -plaintext -d '{
|
|
||||||
"email": "user@example.com",
|
|
||||||
"password": "securepassword123",
|
|
||||||
"username": "testuser"
|
|
||||||
}' localhost:8082 identity.v1.IdentityService/CreateUser
|
|
||||||
|
|
||||||
# Example: Login
|
|
||||||
grpcurl -plaintext -d '{
|
|
||||||
"email": "user@example.com",
|
|
||||||
"password": "securepassword123"
|
|
||||||
}' localhost:8081 auth.v1.AuthService/Login
|
|
||||||
```
|
|
||||||
|
|
||||||
See [Epic 2 Summary](docs/content/stories/epic2/SUMMARY.md) for detailed testing instructions.
|
|
||||||
|
|
||||||
### Configuration
|
### Configuration
|
||||||
|
|
||||||
@@ -188,7 +107,7 @@ export DATABASE_DSN="postgres://user:pass@localhost/dbname"
|
|||||||
export LOGGING_LEVEL=debug
|
export LOGGING_LEVEL=debug
|
||||||
```
|
```
|
||||||
|
|
||||||
## Development
|
## 🛠️ Development
|
||||||
|
|
||||||
### Make Commands
|
### Make Commands
|
||||||
|
|
||||||
@@ -200,27 +119,12 @@ make lint # Run linters
|
|||||||
make fmt # Format code
|
make fmt # Format code
|
||||||
make fmt-check # Check code formatting
|
make fmt-check # Check code formatting
|
||||||
make build # Build platform binary
|
make build # Build platform binary
|
||||||
make generate-proto # Generate gRPC code from protobuf
|
|
||||||
make generate-ent # Generate Ent ORM code
|
|
||||||
make clean # Clean build artifacts
|
make clean # Clean build artifacts
|
||||||
make docker-build # Build Docker image
|
make docker-build # Build Docker image
|
||||||
make docker-run # Run Docker container
|
make docker-run # Run Docker container
|
||||||
make verify # Verify code (fmt, lint, test)
|
make verify # Verify code (fmt, lint, test)
|
||||||
```
|
```
|
||||||
|
|
||||||
### Building Services
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Build individual services
|
|
||||||
go build ./cmd/auth-service
|
|
||||||
go build ./cmd/identity-service
|
|
||||||
go build ./cmd/authz-service
|
|
||||||
go build ./cmd/audit-service
|
|
||||||
|
|
||||||
# Or use make (if targets are defined)
|
|
||||||
make build-services
|
|
||||||
```
|
|
||||||
|
|
||||||
### Running Tests
|
### Running Tests
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
@@ -246,7 +150,7 @@ Run all checks:
|
|||||||
make verify
|
make verify
|
||||||
```
|
```
|
||||||
|
|
||||||
## Documentation
|
## 📚 Documentation
|
||||||
|
|
||||||
Comprehensive documentation is available in the `docs/` directory:
|
Comprehensive documentation is available in the `docs/` directory:
|
||||||
|
|
||||||
@@ -268,7 +172,7 @@ make docs-docker
|
|||||||
|
|
||||||
Documentation will be available at `http://127.0.0.1:8000`
|
Documentation will be available at `http://127.0.0.1:8000`
|
||||||
|
|
||||||
## Architecture
|
## 🏛️ Architecture
|
||||||
|
|
||||||
### Core Kernel
|
### Core Kernel
|
||||||
|
|
||||||
@@ -280,24 +184,6 @@ The platform provides a core kernel with essential services:
|
|||||||
- **Health & Metrics**: Health check endpoints and Prometheus metrics
|
- **Health & Metrics**: Health check endpoints and Prometheus metrics
|
||||||
- **Error Handling**: Centralized error handling with proper error wrapping
|
- **Error Handling**: Centralized error handling with proper error wrapping
|
||||||
|
|
||||||
### Core Services (Epic 2 - ✅ Complete)
|
|
||||||
|
|
||||||
Four independent microservices provide authentication and authorization:
|
|
||||||
|
|
||||||
- **Auth Service** (`cmd/auth-service/`): JWT token generation, validation, refresh tokens
|
|
||||||
- **Identity Service** (`cmd/identity-service/`): User CRUD, password management, email verification
|
|
||||||
- **Authz Service** (`cmd/authz-service/`): RBAC/ABAC authorization, permission resolution
|
|
||||||
- **Audit Service** (`cmd/audit-service/`): Immutable audit logging with querying
|
|
||||||
|
|
||||||
Each service:
|
|
||||||
- Has its own gRPC API
|
|
||||||
- Uses its own database schema
|
|
||||||
- Registers with Consul service registry
|
|
||||||
- Can be deployed independently
|
|
||||||
- Communicates via gRPC clients
|
|
||||||
|
|
||||||
See [Epic 2 Documentation](docs/content/stories/epic2/) for detailed implementation.
|
|
||||||
|
|
||||||
### Module System
|
### Module System
|
||||||
|
|
||||||
Modules extend the platform's functionality by implementing the `IModule` interface:
|
Modules extend the platform's functionality by implementing the `IModule` interface:
|
||||||
@@ -314,11 +200,10 @@ type IModule interface {
|
|||||||
|
|
||||||
### Security
|
### Security
|
||||||
|
|
||||||
- **Authentication**: JWT-based authentication with access and refresh tokens (Auth Service)
|
- **Authentication**: JWT-based authentication with access and refresh tokens
|
||||||
- **User Management**: Secure password hashing with Argon2id, email verification, password reset (Identity Service)
|
- **Authorization**: RBAC/ABAC authorization system
|
||||||
- **Authorization**: RBAC/ABAC authorization system with role and permission management (Authz Service)
|
- **Audit Logging**: Immutable audit trail for security-relevant actions
|
||||||
- **Audit Logging**: Immutable audit trail for security-relevant actions (Audit Service)
|
- **Rate Limiting**: Configurable rate limiting per endpoint
|
||||||
- **Rate Limiting**: Configurable rate limiting per endpoint (planned)
|
|
||||||
|
|
||||||
### Observability
|
### Observability
|
||||||
|
|
||||||
@@ -327,30 +212,27 @@ type IModule interface {
|
|||||||
- **Structured Logging**: JSON-formatted logs with correlation IDs
|
- **Structured Logging**: JSON-formatted logs with correlation IDs
|
||||||
- **Health Checks**: Kubernetes-ready health and readiness endpoints
|
- **Health Checks**: Kubernetes-ready health and readiness endpoints
|
||||||
|
|
||||||
## Configuration
|
## 🔧 Configuration
|
||||||
|
|
||||||
Configuration is managed through YAML files and environment variables. See `config/default.yaml` for the base configuration structure.
|
Configuration is managed through YAML files and environment variables. See `config/default.yaml` for the base configuration structure.
|
||||||
|
|
||||||
Key configuration sections:
|
Key configuration sections:
|
||||||
|
|
||||||
- **Server**: HTTP/gRPC server settings (port, host, timeouts)
|
- **Server**: HTTP server settings (port, host, timeouts)
|
||||||
- **Database**: Database connection settings (PostgreSQL with schema support)
|
- **Database**: Database connection settings
|
||||||
- **Logging**: Log level, format, and output destination
|
- **Logging**: Log level, format, and output destination
|
||||||
- **Authentication**: JWT settings and token configuration
|
- **Authentication**: JWT settings and token configuration
|
||||||
- **Services**: Service-specific ports and hosts
|
|
||||||
- **Registry**: Service registry settings (Consul)
|
|
||||||
|
|
||||||
### Environment Variables
|
## 🧪 Testing
|
||||||
|
|
||||||
Services can be configured via environment variables:
|
The project follows table-driven testing patterns and includes:
|
||||||
|
|
||||||
- `ENVIRONMENT`: `development` or `production`
|
- Unit tests for all packages
|
||||||
- `DATABASE_DSN`: PostgreSQL connection string
|
- Integration tests for service interactions
|
||||||
- `REGISTRY_TYPE`: Service registry type (default: `consul`)
|
- Mock generation for interfaces
|
||||||
- `REGISTRY_CONSUL_ADDRESS`: Consul address (default: `localhost:8500`)
|
- Test coverage reporting
|
||||||
- `AUTH_JWT_SECRET`: JWT signing secret (required for Auth Service)
|
|
||||||
|
|
||||||
## Contributing
|
## 🤝 Contributing
|
||||||
|
|
||||||
1. Create a feature branch: `git checkout -b feature/my-feature`
|
1. Create a feature branch: `git checkout -b feature/my-feature`
|
||||||
2. Make your changes following the project's architecture principles
|
2. Make your changes following the project's architecture principles
|
||||||
@@ -358,41 +240,21 @@ Services can be configured via environment variables:
|
|||||||
4. Commit your changes with clear messages
|
4. Commit your changes with clear messages
|
||||||
5. Push to your branch and create a pull request
|
5. Push to your branch and create a pull request
|
||||||
|
|
||||||
## License
|
## 📄 License
|
||||||
|
|
||||||
[Add license information here]
|
[Add license information here]
|
||||||
|
|
||||||
## Implementation Status
|
## 🔗 Links
|
||||||
|
|
||||||
### ✅ Completed
|
|
||||||
|
|
||||||
- **Epic 0**: Project Setup & Foundation
|
|
||||||
- **Epic 1**: Core Kernel & Infrastructure
|
|
||||||
- **Epic 2**: Core Services (Auth, Identity, Authz, Audit) - **Complete**
|
|
||||||
- All four services implemented as independent microservices
|
|
||||||
- gRPC APIs with service discovery
|
|
||||||
- Database schemas and persistence
|
|
||||||
- Docker support for all services
|
|
||||||
|
|
||||||
### 🚧 In Progress / Planned
|
|
||||||
|
|
||||||
- **Epic 3**: Module Framework
|
|
||||||
- **Epic 4**: Sample Feature Module (Blog)
|
|
||||||
- **Epic 5**: Infrastructure Adapters
|
|
||||||
- **Epic 6**: Observability & Production Readiness
|
|
||||||
- **Epic 7**: Testing, Documentation & CI/CD
|
|
||||||
- **Epic 8**: Advanced Features & Polish
|
|
||||||
|
|
||||||
See [Implementation Plan](docs/content/plan.md) for details.
|
|
||||||
|
|
||||||
## Links
|
|
||||||
|
|
||||||
- [Architecture Documentation](docs/content/architecture/)
|
- [Architecture Documentation](docs/content/architecture/)
|
||||||
- [ADRs](docs/content/adr/)
|
- [ADRs](docs/content/adr/)
|
||||||
- [Implementation Plan](docs/content/plan.md)
|
- [Implementation Plan](docs/content/plan.md)
|
||||||
- [Playbook](docs/content/playbook.md)
|
- [Playbook](docs/content/playbook.md)
|
||||||
- [Epic 2 Summary](docs/content/stories/epic2/SUMMARY.md) - Core Services implementation details
|
|
||||||
|
|
||||||
## Support
|
## 📞 Support
|
||||||
|
|
||||||
For questions and support, please refer to the documentation or create an issue in the repository.
|
For questions and support, please refer to the documentation or create an issue in the repository.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Built with ❤️ using Go**
|
||||||
|
|||||||
@@ -1,56 +0,0 @@
|
|||||||
syntax = "proto3";
|
|
||||||
|
|
||||||
package audit.v1;
|
|
||||||
|
|
||||||
option go_package = "git.dcentral.systems/toolz/goplt/api/proto/generated/audit/v1;auditv1";
|
|
||||||
|
|
||||||
// AuditService provides audit logging operations.
|
|
||||||
service AuditService {
|
|
||||||
// Record records an audit log entry.
|
|
||||||
rpc Record(RecordRequest) returns (RecordResponse);
|
|
||||||
|
|
||||||
// Query queries audit logs based on filters.
|
|
||||||
rpc Query(QueryRequest) returns (QueryResponse);
|
|
||||||
}
|
|
||||||
|
|
||||||
// AuditLogEntry represents an audit log entry.
|
|
||||||
message AuditLogEntry {
|
|
||||||
string user_id = 1;
|
|
||||||
string action = 2; // e.g., "user.create", "user.update"
|
|
||||||
string resource = 3; // e.g., "user", "role"
|
|
||||||
string resource_id = 4;
|
|
||||||
string ip_address = 5;
|
|
||||||
string user_agent = 6;
|
|
||||||
map<string, string> metadata = 7;
|
|
||||||
int64 timestamp = 8;
|
|
||||||
}
|
|
||||||
|
|
||||||
// RecordRequest contains an audit log entry to record.
|
|
||||||
message RecordRequest {
|
|
||||||
AuditLogEntry entry = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
// RecordResponse indicates success.
|
|
||||||
message RecordResponse {
|
|
||||||
bool success = 1;
|
|
||||||
string id = 2; // Audit log entry ID
|
|
||||||
}
|
|
||||||
|
|
||||||
// QueryRequest contains filters for querying audit logs.
|
|
||||||
message QueryRequest {
|
|
||||||
optional string user_id = 1;
|
|
||||||
optional string action = 2;
|
|
||||||
optional string resource = 3;
|
|
||||||
optional string resource_id = 4;
|
|
||||||
optional int64 start_time = 5;
|
|
||||||
optional int64 end_time = 6;
|
|
||||||
int32 limit = 7; // Max number of results
|
|
||||||
int32 offset = 8; // Pagination offset
|
|
||||||
}
|
|
||||||
|
|
||||||
// QueryResponse contains audit log entries.
|
|
||||||
message QueryResponse {
|
|
||||||
repeated AuditLogEntry entries = 1;
|
|
||||||
int32 total = 2; // Total number of matching entries
|
|
||||||
}
|
|
||||||
|
|
||||||
@@ -1,71 +0,0 @@
|
|||||||
syntax = "proto3";
|
|
||||||
|
|
||||||
package auth.v1;
|
|
||||||
|
|
||||||
option go_package = "git.dcentral.systems/toolz/goplt/api/proto/generated/auth/v1;authv1";
|
|
||||||
|
|
||||||
// AuthService provides authentication operations.
|
|
||||||
service AuthService {
|
|
||||||
// Login authenticates a user and returns access and refresh tokens.
|
|
||||||
rpc Login(LoginRequest) returns (LoginResponse);
|
|
||||||
|
|
||||||
// RefreshToken refreshes an access token using a refresh token.
|
|
||||||
rpc RefreshToken(RefreshTokenRequest) returns (RefreshTokenResponse);
|
|
||||||
|
|
||||||
// ValidateToken validates a JWT token and returns the token claims.
|
|
||||||
rpc ValidateToken(ValidateTokenRequest) returns (ValidateTokenResponse);
|
|
||||||
|
|
||||||
// Logout invalidates a refresh token.
|
|
||||||
rpc Logout(LogoutRequest) returns (LogoutResponse);
|
|
||||||
}
|
|
||||||
|
|
||||||
// LoginRequest contains login credentials.
|
|
||||||
message LoginRequest {
|
|
||||||
string email = 1;
|
|
||||||
string password = 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
// LoginResponse contains authentication tokens.
|
|
||||||
message LoginResponse {
|
|
||||||
string access_token = 1;
|
|
||||||
string refresh_token = 2;
|
|
||||||
int64 expires_in = 3; // seconds
|
|
||||||
string token_type = 4; // "Bearer"
|
|
||||||
}
|
|
||||||
|
|
||||||
// RefreshTokenRequest contains a refresh token.
|
|
||||||
message RefreshTokenRequest {
|
|
||||||
string refresh_token = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
// RefreshTokenResponse contains new authentication tokens.
|
|
||||||
message RefreshTokenResponse {
|
|
||||||
string access_token = 1;
|
|
||||||
string refresh_token = 2;
|
|
||||||
int64 expires_in = 3; // seconds
|
|
||||||
string token_type = 4; // "Bearer"
|
|
||||||
}
|
|
||||||
|
|
||||||
// ValidateTokenRequest contains a JWT token to validate.
|
|
||||||
message ValidateTokenRequest {
|
|
||||||
string token = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
// ValidateTokenResponse contains token claims.
|
|
||||||
message ValidateTokenResponse {
|
|
||||||
string user_id = 1;
|
|
||||||
string email = 2;
|
|
||||||
repeated string roles = 3;
|
|
||||||
int64 expires_at = 4;
|
|
||||||
}
|
|
||||||
|
|
||||||
// LogoutRequest contains a refresh token to invalidate.
|
|
||||||
message LogoutRequest {
|
|
||||||
string refresh_token = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
// LogoutResponse indicates success.
|
|
||||||
message LogoutResponse {
|
|
||||||
bool success = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
@@ -1,80 +0,0 @@
|
|||||||
syntax = "proto3";
|
|
||||||
|
|
||||||
package authz.v1;
|
|
||||||
|
|
||||||
option go_package = "git.dcentral.systems/toolz/goplt/api/proto/generated/authz/v1;authzv1";
|
|
||||||
|
|
||||||
// AuthzService provides authorization operations.
|
|
||||||
service AuthzService {
|
|
||||||
// Authorize checks if a user has a specific permission and returns an error if not.
|
|
||||||
rpc Authorize(AuthorizeRequest) returns (AuthorizeResponse);
|
|
||||||
|
|
||||||
// HasPermission checks if a user has a specific permission.
|
|
||||||
rpc HasPermission(HasPermissionRequest) returns (HasPermissionResponse);
|
|
||||||
|
|
||||||
// GetUserPermissions returns all permissions for a user.
|
|
||||||
rpc GetUserPermissions(GetUserPermissionsRequest) returns (GetUserPermissionsResponse);
|
|
||||||
|
|
||||||
// GetUserRoles returns all roles for a user.
|
|
||||||
rpc GetUserRoles(GetUserRolesRequest) returns (GetUserRolesResponse);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Permission represents a permission in the system.
|
|
||||||
message Permission {
|
|
||||||
string id = 1;
|
|
||||||
string code = 2;
|
|
||||||
string name = 3;
|
|
||||||
string description = 4;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Role represents a role in the system.
|
|
||||||
message Role {
|
|
||||||
string id = 1;
|
|
||||||
string name = 2;
|
|
||||||
string description = 3;
|
|
||||||
repeated string permissions = 4; // Permission codes
|
|
||||||
}
|
|
||||||
|
|
||||||
// AuthorizeRequest contains user ID and permission to check.
|
|
||||||
message AuthorizeRequest {
|
|
||||||
string user_id = 1;
|
|
||||||
string permission = 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
// AuthorizeResponse indicates authorization result.
|
|
||||||
message AuthorizeResponse {
|
|
||||||
bool authorized = 1;
|
|
||||||
string message = 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
// HasPermissionRequest contains user ID and permission to check.
|
|
||||||
message HasPermissionRequest {
|
|
||||||
string user_id = 1;
|
|
||||||
string permission = 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
// HasPermissionResponse indicates if the user has the permission.
|
|
||||||
message HasPermissionResponse {
|
|
||||||
bool has_permission = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetUserPermissionsRequest contains a user ID.
|
|
||||||
message GetUserPermissionsRequest {
|
|
||||||
string user_id = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetUserPermissionsResponse contains all permissions for the user.
|
|
||||||
message GetUserPermissionsResponse {
|
|
||||||
repeated Permission permissions = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetUserRolesRequest contains a user ID.
|
|
||||||
message GetUserRolesRequest {
|
|
||||||
string user_id = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetUserRolesResponse contains all roles for the user.
|
|
||||||
message GetUserRolesResponse {
|
|
||||||
repeated Role roles = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
@@ -1,148 +0,0 @@
|
|||||||
syntax = "proto3";
|
|
||||||
|
|
||||||
package identity.v1;
|
|
||||||
|
|
||||||
option go_package = "git.dcentral.systems/toolz/goplt/api/proto/generated/identity/v1;identityv1";
|
|
||||||
|
|
||||||
// IdentityService provides user management operations.
|
|
||||||
service IdentityService {
|
|
||||||
// GetUser retrieves a user by ID.
|
|
||||||
rpc GetUser(GetUserRequest) returns (GetUserResponse);
|
|
||||||
|
|
||||||
// GetUserByEmail retrieves a user by email address.
|
|
||||||
rpc GetUserByEmail(GetUserByEmailRequest) returns (GetUserByEmailResponse);
|
|
||||||
|
|
||||||
// CreateUser creates a new user.
|
|
||||||
rpc CreateUser(CreateUserRequest) returns (CreateUserResponse);
|
|
||||||
|
|
||||||
// UpdateUser updates an existing user.
|
|
||||||
rpc UpdateUser(UpdateUserRequest) returns (UpdateUserResponse);
|
|
||||||
|
|
||||||
// DeleteUser deletes a user.
|
|
||||||
rpc DeleteUser(DeleteUserRequest) returns (DeleteUserResponse);
|
|
||||||
|
|
||||||
// VerifyEmail verifies a user's email address using a verification token.
|
|
||||||
rpc VerifyEmail(VerifyEmailRequest) returns (VerifyEmailResponse);
|
|
||||||
|
|
||||||
// RequestPasswordReset requests a password reset token.
|
|
||||||
rpc RequestPasswordReset(RequestPasswordResetRequest) returns (RequestPasswordResetResponse);
|
|
||||||
|
|
||||||
// ResetPassword resets a user's password using a reset token.
|
|
||||||
rpc ResetPassword(ResetPasswordRequest) returns (ResetPasswordResponse);
|
|
||||||
|
|
||||||
// VerifyPassword verifies a user's password.
|
|
||||||
rpc VerifyPassword(VerifyPasswordRequest) returns (VerifyPasswordResponse);
|
|
||||||
}
|
|
||||||
|
|
||||||
// User represents a user in the system.
|
|
||||||
message User {
|
|
||||||
string id = 1;
|
|
||||||
string email = 2;
|
|
||||||
string username = 3;
|
|
||||||
string first_name = 4;
|
|
||||||
string last_name = 5;
|
|
||||||
bool email_verified = 6;
|
|
||||||
int64 created_at = 7;
|
|
||||||
int64 updated_at = 8;
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetUserRequest contains a user ID.
|
|
||||||
message GetUserRequest {
|
|
||||||
string id = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetUserResponse contains a user.
|
|
||||||
message GetUserResponse {
|
|
||||||
User user = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetUserByEmailRequest contains an email address.
|
|
||||||
message GetUserByEmailRequest {
|
|
||||||
string email = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetUserByEmailResponse contains a user.
|
|
||||||
message GetUserByEmailResponse {
|
|
||||||
User user = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
// CreateUserRequest contains user data for creation.
|
|
||||||
message CreateUserRequest {
|
|
||||||
string email = 1;
|
|
||||||
string username = 2;
|
|
||||||
string password = 3;
|
|
||||||
string first_name = 4;
|
|
||||||
string last_name = 5;
|
|
||||||
}
|
|
||||||
|
|
||||||
// CreateUserResponse contains the created user.
|
|
||||||
message CreateUserResponse {
|
|
||||||
User user = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
// UpdateUserRequest contains user data for update.
|
|
||||||
message UpdateUserRequest {
|
|
||||||
string id = 1;
|
|
||||||
optional string email = 2;
|
|
||||||
optional string username = 3;
|
|
||||||
optional string first_name = 4;
|
|
||||||
optional string last_name = 5;
|
|
||||||
}
|
|
||||||
|
|
||||||
// UpdateUserResponse contains the updated user.
|
|
||||||
message UpdateUserResponse {
|
|
||||||
User user = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
// DeleteUserRequest contains a user ID.
|
|
||||||
message DeleteUserRequest {
|
|
||||||
string id = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
// DeleteUserResponse indicates success.
|
|
||||||
message DeleteUserResponse {
|
|
||||||
bool success = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
// VerifyEmailRequest contains a verification token.
|
|
||||||
message VerifyEmailRequest {
|
|
||||||
string token = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
// VerifyEmailResponse indicates success.
|
|
||||||
message VerifyEmailResponse {
|
|
||||||
bool success = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
// RequestPasswordResetRequest contains an email address.
|
|
||||||
message RequestPasswordResetRequest {
|
|
||||||
string email = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
// RequestPasswordResetResponse indicates success.
|
|
||||||
message RequestPasswordResetResponse {
|
|
||||||
bool success = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
// ResetPasswordRequest contains a reset token and new password.
|
|
||||||
message ResetPasswordRequest {
|
|
||||||
string token = 1;
|
|
||||||
string new_password = 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
// ResetPasswordResponse indicates success.
|
|
||||||
message ResetPasswordResponse {
|
|
||||||
bool success = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
// VerifyPasswordRequest contains email and password.
|
|
||||||
message VerifyPasswordRequest {
|
|
||||||
string email = 1;
|
|
||||||
string password = 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
// VerifyPasswordResponse contains the user if password is valid.
|
|
||||||
message VerifyPasswordResponse {
|
|
||||||
User user = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
@@ -1,40 +0,0 @@
|
|||||||
FROM alpine:latest
|
|
||||||
|
|
||||||
# Install system dependencies
|
|
||||||
RUN apk add --no-cache \
|
|
||||||
nodejs \
|
|
||||||
npm \
|
|
||||||
gcc \
|
|
||||||
build-base \
|
|
||||||
musl-dev \
|
|
||||||
curl \
|
|
||||||
make \
|
|
||||||
wget \
|
|
||||||
tar \
|
|
||||||
bash \
|
|
||||||
git \
|
|
||||||
protobuf \
|
|
||||||
protobuf-dev
|
|
||||||
|
|
||||||
# Install Go 1.25.3
|
|
||||||
RUN cd /tmp && \
|
|
||||||
wget -q https://go.dev/dl/go1.25.3.linux-amd64.tar.gz && \
|
|
||||||
tar -C /usr/local -xzf go1.25.3.linux-amd64.tar.gz && \
|
|
||||||
rm go1.25.3.linux-amd64.tar.gz
|
|
||||||
|
|
||||||
# Set up Go environment
|
|
||||||
ENV PATH=/usr/local/go/bin:$PATH:/root/go/bin
|
|
||||||
ENV GOROOT=/usr/local/go
|
|
||||||
ENV GOPATH=/root/go
|
|
||||||
ENV CGO_ENABLED=1
|
|
||||||
ENV GOFLAGS=-buildvcs=false
|
|
||||||
|
|
||||||
# Install Go protobuf plugins
|
|
||||||
RUN go install google.golang.org/protobuf/cmd/protoc-gen-go@latest && \
|
|
||||||
go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest
|
|
||||||
|
|
||||||
# Install golangci-lint
|
|
||||||
RUN curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b /root/go/bin
|
|
||||||
|
|
||||||
# Set working directory
|
|
||||||
WORKDIR /workspace
|
|
||||||
@@ -1,36 +0,0 @@
|
|||||||
# Build stage
|
|
||||||
FROM golang:1.25-alpine AS builder
|
|
||||||
|
|
||||||
WORKDIR /build
|
|
||||||
|
|
||||||
# Install build dependencies
|
|
||||||
RUN apk add --no-cache git make
|
|
||||||
|
|
||||||
# Copy go mod files
|
|
||||||
COPY go.mod go.sum ./
|
|
||||||
RUN go mod download
|
|
||||||
|
|
||||||
# Copy source code
|
|
||||||
COPY . .
|
|
||||||
|
|
||||||
# Build the service
|
|
||||||
WORKDIR /build/cmd/api-gateway
|
|
||||||
RUN CGO_ENABLED=0 GOOS=linux go build -o api-gateway -a -installsuffix cgo .
|
|
||||||
|
|
||||||
# Runtime stage
|
|
||||||
FROM alpine:latest
|
|
||||||
|
|
||||||
RUN apk --no-cache add ca-certificates tzdata
|
|
||||||
|
|
||||||
WORKDIR /app
|
|
||||||
|
|
||||||
# Copy binary from builder
|
|
||||||
COPY --from=builder /build/cmd/api-gateway/api-gateway .
|
|
||||||
|
|
||||||
# Copy config files
|
|
||||||
COPY --from=builder /build/config ./config
|
|
||||||
|
|
||||||
EXPOSE 8080
|
|
||||||
|
|
||||||
CMD ["./api-gateway"]
|
|
||||||
|
|
||||||
@@ -1,168 +0,0 @@
|
|||||||
// Package main provides the API Gateway service entry point.
|
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
"net/http"
|
|
||||||
"os"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"git.dcentral.systems/toolz/goplt/internal/client"
|
|
||||||
"git.dcentral.systems/toolz/goplt/internal/di"
|
|
||||||
"git.dcentral.systems/toolz/goplt/internal/health"
|
|
||||||
"git.dcentral.systems/toolz/goplt/internal/metrics"
|
|
||||||
"git.dcentral.systems/toolz/goplt/internal/server"
|
|
||||||
"git.dcentral.systems/toolz/goplt/pkg/config"
|
|
||||||
"git.dcentral.systems/toolz/goplt/pkg/errorbus"
|
|
||||||
"git.dcentral.systems/toolz/goplt/pkg/logger"
|
|
||||||
"git.dcentral.systems/toolz/goplt/pkg/registry"
|
|
||||||
"git.dcentral.systems/toolz/goplt/services/gateway"
|
|
||||||
"go.opentelemetry.io/otel/trace"
|
|
||||||
"go.uber.org/fx"
|
|
||||||
)
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
// Create DI container with core kernel services
|
|
||||||
container := di.NewContainer(
|
|
||||||
// Invoke lifecycle hooks
|
|
||||||
fx.Invoke(di.RegisterLifecycleHooks),
|
|
||||||
// Create API Gateway
|
|
||||||
fx.Invoke(func(
|
|
||||||
cfg config.ConfigProvider,
|
|
||||||
log logger.Logger,
|
|
||||||
healthRegistry *health.Registry,
|
|
||||||
metricsRegistry *metrics.Metrics,
|
|
||||||
errorBus errorbus.ErrorPublisher,
|
|
||||||
tracer trace.TracerProvider,
|
|
||||||
serviceRegistry registry.ServiceRegistry,
|
|
||||||
clientFactory *client.ServiceClientFactory,
|
|
||||||
lc fx.Lifecycle,
|
|
||||||
) {
|
|
||||||
// Create HTTP server using server foundation
|
|
||||||
srv, err := server.NewServer(cfg, log, healthRegistry, metricsRegistry, errorBus, tracer)
|
|
||||||
if err != nil {
|
|
||||||
log.Error("Failed to create API Gateway server",
|
|
||||||
logger.Error(err),
|
|
||||||
)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Setup gateway routes
|
|
||||||
gateway, err := gateway.NewGateway(cfg, log, clientFactory, serviceRegistry)
|
|
||||||
if err != nil {
|
|
||||||
log.Error("Failed to create API Gateway",
|
|
||||||
logger.Error(err),
|
|
||||||
)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
gateway.SetupRoutes(srv.Router())
|
|
||||||
|
|
||||||
// Determine port and host for registration
|
|
||||||
gatewayPort := cfg.GetInt("gateway.port")
|
|
||||||
if gatewayPort == 0 {
|
|
||||||
gatewayPort = cfg.GetInt("server.port")
|
|
||||||
if gatewayPort == 0 {
|
|
||||||
gatewayPort = 8080
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// In Docker, always use the Docker service name for health checks
|
|
||||||
// Consul (also in Docker) needs to reach the service via Docker DNS
|
|
||||||
gatewayHost := cfg.GetString("gateway.host")
|
|
||||||
if os.Getenv("ENVIRONMENT") == "production" || os.Getenv("DOCKER") == "true" {
|
|
||||||
gatewayHost = "api-gateway" // Docker service name - required for Consul health checks
|
|
||||||
} else if gatewayHost == "" {
|
|
||||||
gatewayHost = cfg.GetString("server.host")
|
|
||||||
if gatewayHost == "" || gatewayHost == "0.0.0.0" {
|
|
||||||
gatewayHost = "localhost" // Local development
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
serviceInstance := ®istry.ServiceInstance{
|
|
||||||
ID: fmt.Sprintf("api-gateway-%d", os.Getpid()),
|
|
||||||
Name: "api-gateway",
|
|
||||||
Address: gatewayHost,
|
|
||||||
Port: gatewayPort,
|
|
||||||
Tags: []string{"gateway", "http"},
|
|
||||||
Metadata: map[string]string{
|
|
||||||
"version": "1.0.0",
|
|
||||||
"protocol": "http",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
// Register lifecycle hooks
|
|
||||||
lc.Append(fx.Hook{
|
|
||||||
OnStart: func(ctx context.Context) error {
|
|
||||||
// Register with service registry
|
|
||||||
if err := serviceRegistry.Register(ctx, serviceInstance); err != nil {
|
|
||||||
log.Warn("Failed to register API Gateway with service registry",
|
|
||||||
logger.Error(err),
|
|
||||||
)
|
|
||||||
// Continue anyway - gateway can work without registry
|
|
||||||
} else {
|
|
||||||
log.Info("API Gateway registered with service registry",
|
|
||||||
logger.String("service_id", serviceInstance.ID),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Start HTTP server
|
|
||||||
addr := fmt.Sprintf("%s:%d", cfg.GetString("server.host"), gatewayPort)
|
|
||||||
log.Info("API Gateway starting",
|
|
||||||
logger.String("addr", addr),
|
|
||||||
)
|
|
||||||
|
|
||||||
errChan := make(chan error, 1)
|
|
||||||
go func() {
|
|
||||||
if err := srv.Start(); err != nil && err != http.ErrServerClosed {
|
|
||||||
log.Error("API Gateway server failed",
|
|
||||||
logger.String("error", err.Error()),
|
|
||||||
)
|
|
||||||
errChan <- err
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
// Wait a short time to detect immediate binding errors
|
|
||||||
select {
|
|
||||||
case err := <-errChan:
|
|
||||||
return fmt.Errorf("API Gateway failed to start: %w", err)
|
|
||||||
case <-time.After(500 * time.Millisecond):
|
|
||||||
log.Info("API Gateway started successfully",
|
|
||||||
logger.String("addr", addr),
|
|
||||||
)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
},
|
|
||||||
OnStop: func(ctx context.Context) error {
|
|
||||||
// Deregister from service registry
|
|
||||||
if err := serviceRegistry.Deregister(ctx, serviceInstance.ID); err != nil {
|
|
||||||
log.Warn("Failed to deregister API Gateway from service registry",
|
|
||||||
logger.Error(err),
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
log.Info("API Gateway deregistered from service registry")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Shutdown HTTP server
|
|
||||||
return srv.Shutdown(ctx)
|
|
||||||
},
|
|
||||||
})
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
|
|
||||||
// Create root context
|
|
||||||
ctx := context.Background()
|
|
||||||
|
|
||||||
// Start the application
|
|
||||||
if err := container.Start(ctx); err != nil {
|
|
||||||
log := logger.GetGlobalLogger()
|
|
||||||
if log != nil {
|
|
||||||
log.Error("Failed to start API Gateway",
|
|
||||||
logger.Error(err),
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
fmt.Fprintf(os.Stderr, "Failed to start API Gateway: %v\n", err)
|
|
||||||
}
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,156 +0,0 @@
|
|||||||
// Package main provides tests for the API Gateway service entry point.
|
|
||||||
// Note: Full integration tests for the API Gateway should be in integration test suite
|
|
||||||
// with testcontainers for service discovery and backend services.
|
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"testing"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"git.dcentral.systems/toolz/goplt/internal/di"
|
|
||||||
"git.dcentral.systems/toolz/goplt/pkg/registry"
|
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
"github.com/stretchr/testify/require"
|
|
||||||
"go.uber.org/fx"
|
|
||||||
)
|
|
||||||
|
|
||||||
// TestAPIGatewaySetup verifies that the API Gateway setup structure is correct.
|
|
||||||
// Note: Full DI setup requires config files, so this test verifies the structure
|
|
||||||
// without actually starting the container.
|
|
||||||
func TestAPIGatewaySetup(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
// Verify that container can be created
|
|
||||||
// Full setup requires config files which are not available in unit tests
|
|
||||||
container := di.NewContainer()
|
|
||||||
require.NotNil(t, container)
|
|
||||||
|
|
||||||
// Test that container can be stopped (without starting)
|
|
||||||
// This verifies the container structure is correct
|
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second)
|
|
||||||
defer cancel()
|
|
||||||
|
|
||||||
// Stop should work even if container wasn't started
|
|
||||||
err := container.Stop(ctx)
|
|
||||||
// It's okay if it errors - we're just testing structure
|
|
||||||
_ = err
|
|
||||||
}
|
|
||||||
|
|
||||||
// TestServiceInstanceCreation verifies that service instance is created correctly.
|
|
||||||
func TestServiceInstanceCreation(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
tests := []struct {
|
|
||||||
name string
|
|
||||||
host string
|
|
||||||
port int
|
|
||||||
expected string
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
name: "default host and port",
|
|
||||||
host: "",
|
|
||||||
port: 0,
|
|
||||||
expected: "localhost:8080",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "custom host and port",
|
|
||||||
host: "gateway.example.com",
|
|
||||||
port: 9090,
|
|
||||||
expected: "gateway.example.com:9090",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "custom host default port",
|
|
||||||
host: "gateway.example.com",
|
|
||||||
port: 0,
|
|
||||||
expected: "gateway.example.com:8080",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, tt := range tests {
|
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
|
||||||
// Simulate service instance creation logic from main.go
|
|
||||||
gatewayPort := tt.port
|
|
||||||
if gatewayPort == 0 {
|
|
||||||
gatewayPort = 8080
|
|
||||||
}
|
|
||||||
gatewayHost := tt.host
|
|
||||||
if gatewayHost == "" {
|
|
||||||
gatewayHost = "localhost"
|
|
||||||
}
|
|
||||||
|
|
||||||
serviceInstance := ®istry.ServiceInstance{
|
|
||||||
ID: "api-gateway-test",
|
|
||||||
Name: "api-gateway",
|
|
||||||
Address: gatewayHost,
|
|
||||||
Port: gatewayPort,
|
|
||||||
Tags: []string{"gateway", "http"},
|
|
||||||
Metadata: map[string]string{
|
|
||||||
"version": "1.0.0",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
assert.Equal(t, "api-gateway", serviceInstance.Name)
|
|
||||||
assert.Equal(t, gatewayHost, serviceInstance.Address)
|
|
||||||
assert.Equal(t, gatewayPort, serviceInstance.Port)
|
|
||||||
assert.Contains(t, serviceInstance.Tags, "gateway")
|
|
||||||
assert.Contains(t, serviceInstance.Tags, "http")
|
|
||||||
assert.Equal(t, "1.0.0", serviceInstance.Metadata["version"])
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// TestLifecycleHooksStructure verifies that lifecycle hooks can be registered.
|
|
||||||
// Note: Full lifecycle testing requires config files and should be done in integration tests.
|
|
||||||
func TestLifecycleHooksStructure(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
var onStartCalled bool
|
|
||||||
var onStopCalled bool
|
|
||||||
|
|
||||||
// Create a test container with custom lifecycle hooks (without core module)
|
|
||||||
// This tests the hook registration mechanism
|
|
||||||
container := di.NewContainer(
|
|
||||||
fx.Invoke(func(lc fx.Lifecycle) {
|
|
||||||
lc.Append(fx.Hook{
|
|
||||||
OnStart: func(ctx context.Context) error {
|
|
||||||
onStartCalled = true
|
|
||||||
return nil
|
|
||||||
},
|
|
||||||
OnStop: func(ctx context.Context) error {
|
|
||||||
onStopCalled = true
|
|
||||||
return nil
|
|
||||||
},
|
|
||||||
})
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
|
|
||||||
require.NotNil(t, container)
|
|
||||||
|
|
||||||
// Start the container to trigger OnStart
|
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), 100*time.Millisecond)
|
|
||||||
defer cancel()
|
|
||||||
|
|
||||||
// Start in goroutine since it blocks on signal
|
|
||||||
go func() {
|
|
||||||
_ = container.Start(ctx)
|
|
||||||
}()
|
|
||||||
|
|
||||||
// Give it a moment to start
|
|
||||||
time.Sleep(50 * time.Millisecond)
|
|
||||||
|
|
||||||
// Stop to trigger OnStop
|
|
||||||
stopCtx, stopCancel := context.WithTimeout(context.Background(), 100*time.Millisecond)
|
|
||||||
defer stopCancel()
|
|
||||||
|
|
||||||
err := container.Stop(stopCtx)
|
|
||||||
// Stop may error if container wasn't fully started, which is okay
|
|
||||||
_ = err
|
|
||||||
|
|
||||||
// Verify hooks were called
|
|
||||||
// Note: OnStart may not be called if container fails to start due to missing config
|
|
||||||
// This is expected in unit tests - full testing should be in integration tests
|
|
||||||
if onStartCalled {
|
|
||||||
assert.True(t, onStopCalled, "OnStop should be called if OnStart was called")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,36 +0,0 @@
|
|||||||
# Build stage
|
|
||||||
FROM golang:1.25-alpine AS builder
|
|
||||||
|
|
||||||
WORKDIR /build
|
|
||||||
|
|
||||||
# Install build dependencies
|
|
||||||
RUN apk add --no-cache git make
|
|
||||||
|
|
||||||
# Copy go mod files
|
|
||||||
COPY go.mod go.sum ./
|
|
||||||
RUN go mod download
|
|
||||||
|
|
||||||
# Copy source code
|
|
||||||
COPY . .
|
|
||||||
|
|
||||||
# Build the service
|
|
||||||
WORKDIR /build/cmd/audit-service
|
|
||||||
RUN CGO_ENABLED=0 GOOS=linux go build -o audit-service -a -installsuffix cgo .
|
|
||||||
|
|
||||||
# Runtime stage
|
|
||||||
FROM alpine:latest
|
|
||||||
|
|
||||||
RUN apk --no-cache add ca-certificates tzdata
|
|
||||||
|
|
||||||
WORKDIR /app
|
|
||||||
|
|
||||||
# Copy binary from builder
|
|
||||||
COPY --from=builder /build/cmd/audit-service/audit-service .
|
|
||||||
|
|
||||||
# Copy config files
|
|
||||||
COPY --from=builder /build/config ./config
|
|
||||||
|
|
||||||
EXPOSE 8084
|
|
||||||
|
|
||||||
CMD ["./audit-service"]
|
|
||||||
|
|
||||||
@@ -1,351 +0,0 @@
|
|||||||
// Package main provides FX providers for Audit Service.
|
|
||||||
// This file creates the service inline to avoid importing internal packages.
|
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
"math"
|
|
||||||
"net"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
auditv1 "git.dcentral.systems/toolz/goplt/api/proto/generated/audit/v1"
|
|
||||||
"git.dcentral.systems/toolz/goplt/internal/ent"
|
|
||||||
"git.dcentral.systems/toolz/goplt/internal/ent/auditlog"
|
|
||||||
"git.dcentral.systems/toolz/goplt/internal/infra/database"
|
|
||||||
"git.dcentral.systems/toolz/goplt/pkg/config"
|
|
||||||
"git.dcentral.systems/toolz/goplt/pkg/logger"
|
|
||||||
"go.uber.org/fx"
|
|
||||||
"go.uber.org/zap"
|
|
||||||
"google.golang.org/grpc"
|
|
||||||
"google.golang.org/grpc/codes"
|
|
||||||
"google.golang.org/grpc/health"
|
|
||||||
"google.golang.org/grpc/health/grpc_health_v1"
|
|
||||||
"google.golang.org/grpc/reflection"
|
|
||||||
"google.golang.org/grpc/status"
|
|
||||||
)
|
|
||||||
|
|
||||||
// auditLogEntry represents an audit log entry.
|
|
||||||
type auditLogEntry struct {
|
|
||||||
UserID string
|
|
||||||
Action string
|
|
||||||
Resource string
|
|
||||||
ResourceID string
|
|
||||||
IPAddress string
|
|
||||||
UserAgent string
|
|
||||||
Metadata map[string]string
|
|
||||||
Timestamp int64
|
|
||||||
}
|
|
||||||
|
|
||||||
// auditLogFilters contains filters for querying audit logs.
|
|
||||||
type auditLogFilters struct {
|
|
||||||
UserID *string
|
|
||||||
Action *string
|
|
||||||
Resource *string
|
|
||||||
ResourceID *string
|
|
||||||
StartTime *int64
|
|
||||||
EndTime *int64
|
|
||||||
Limit int
|
|
||||||
Offset int
|
|
||||||
}
|
|
||||||
|
|
||||||
// auditService provides audit logging functionality.
|
|
||||||
type auditService struct {
|
|
||||||
client *database.Client
|
|
||||||
logger logger.Logger
|
|
||||||
}
|
|
||||||
|
|
||||||
// record records an audit log entry.
|
|
||||||
func (s *auditService) record(ctx context.Context, entry *auditLogEntry) error {
|
|
||||||
// Convert metadata map to JSON
|
|
||||||
metadataJSON := make(map[string]interface{})
|
|
||||||
for k, v := range entry.Metadata {
|
|
||||||
metadataJSON[k] = v
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create audit log entry
|
|
||||||
timestamp := time.Unix(entry.Timestamp, 0)
|
|
||||||
if entry.Timestamp == 0 {
|
|
||||||
timestamp = time.Now()
|
|
||||||
}
|
|
||||||
|
|
||||||
create := s.client.AuditLog.Create().
|
|
||||||
SetID(fmt.Sprintf("%d-%d", time.Now().Unix(), time.Now().UnixNano()%1000000)).
|
|
||||||
SetUserID(entry.UserID).
|
|
||||||
SetAction(entry.Action).
|
|
||||||
SetMetadata(metadataJSON).
|
|
||||||
SetTimestamp(timestamp)
|
|
||||||
|
|
||||||
if entry.Resource != "" {
|
|
||||||
create = create.SetResource(entry.Resource)
|
|
||||||
}
|
|
||||||
if entry.ResourceID != "" {
|
|
||||||
create = create.SetResourceID(entry.ResourceID)
|
|
||||||
}
|
|
||||||
if entry.IPAddress != "" {
|
|
||||||
create = create.SetIPAddress(entry.IPAddress)
|
|
||||||
}
|
|
||||||
if entry.UserAgent != "" {
|
|
||||||
create = create.SetUserAgent(entry.UserAgent)
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err := create.Save(ctx)
|
|
||||||
if err != nil {
|
|
||||||
s.logger.Error("Failed to record audit log",
|
|
||||||
zap.Error(err),
|
|
||||||
zap.String("user_id", entry.UserID),
|
|
||||||
zap.String("action", entry.Action),
|
|
||||||
)
|
|
||||||
return fmt.Errorf("failed to record audit log: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// query queries audit logs based on filters.
|
|
||||||
func (s *auditService) query(ctx context.Context, filters *auditLogFilters) ([]*auditLogEntry, error) {
|
|
||||||
query := s.client.AuditLog.Query()
|
|
||||||
|
|
||||||
// Apply filters
|
|
||||||
if filters.UserID != nil {
|
|
||||||
query = query.Where(auditlog.UserID(*filters.UserID))
|
|
||||||
}
|
|
||||||
if filters.Action != nil {
|
|
||||||
query = query.Where(auditlog.Action(*filters.Action))
|
|
||||||
}
|
|
||||||
if filters.Resource != nil {
|
|
||||||
query = query.Where(auditlog.Resource(*filters.Resource))
|
|
||||||
}
|
|
||||||
if filters.ResourceID != nil {
|
|
||||||
query = query.Where(auditlog.ResourceID(*filters.ResourceID))
|
|
||||||
}
|
|
||||||
if filters.StartTime != nil {
|
|
||||||
query = query.Where(auditlog.TimestampGTE(time.Unix(*filters.StartTime, 0)))
|
|
||||||
}
|
|
||||||
if filters.EndTime != nil {
|
|
||||||
query = query.Where(auditlog.TimestampLTE(time.Unix(*filters.EndTime, 0)))
|
|
||||||
}
|
|
||||||
|
|
||||||
// Apply pagination
|
|
||||||
if filters.Limit > 0 {
|
|
||||||
query = query.Limit(filters.Limit)
|
|
||||||
}
|
|
||||||
if filters.Offset > 0 {
|
|
||||||
query = query.Offset(filters.Offset)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Order by timestamp descending
|
|
||||||
query = query.Order(ent.Desc(auditlog.FieldTimestamp))
|
|
||||||
|
|
||||||
// Execute query
|
|
||||||
auditLogs, err := query.All(ctx)
|
|
||||||
if err != nil {
|
|
||||||
s.logger.Error("Failed to query audit logs",
|
|
||||||
zap.Error(err),
|
|
||||||
)
|
|
||||||
return nil, fmt.Errorf("failed to query audit logs: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Convert to service entries
|
|
||||||
entries := make([]*auditLogEntry, 0, len(auditLogs))
|
|
||||||
for _, log := range auditLogs {
|
|
||||||
// Convert metadata from map[string]interface{} to map[string]string
|
|
||||||
metadata := make(map[string]string)
|
|
||||||
if log.Metadata != nil {
|
|
||||||
for k, v := range log.Metadata {
|
|
||||||
if str, ok := v.(string); ok {
|
|
||||||
metadata[k] = str
|
|
||||||
} else {
|
|
||||||
metadata[k] = fmt.Sprintf("%v", v)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
entry := &auditLogEntry{
|
|
||||||
UserID: log.UserID,
|
|
||||||
Action: log.Action,
|
|
||||||
Resource: log.Resource,
|
|
||||||
ResourceID: log.ResourceID,
|
|
||||||
IPAddress: log.IPAddress,
|
|
||||||
UserAgent: log.UserAgent,
|
|
||||||
Metadata: metadata,
|
|
||||||
Timestamp: log.Timestamp.Unix(),
|
|
||||||
}
|
|
||||||
entries = append(entries, entry)
|
|
||||||
}
|
|
||||||
|
|
||||||
return entries, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// auditServerImpl implements the AuditService gRPC server.
|
|
||||||
type auditServerImpl struct {
|
|
||||||
auditv1.UnimplementedAuditServiceServer
|
|
||||||
service *auditService
|
|
||||||
logger *zap.Logger
|
|
||||||
}
|
|
||||||
|
|
||||||
// Record records an audit log entry.
|
|
||||||
func (s *auditServerImpl) Record(ctx context.Context, req *auditv1.RecordRequest) (*auditv1.RecordResponse, error) {
|
|
||||||
if req.Entry == nil {
|
|
||||||
return nil, status.Error(codes.InvalidArgument, "entry is required")
|
|
||||||
}
|
|
||||||
|
|
||||||
entry := req.Entry
|
|
||||||
|
|
||||||
// Convert proto entry to service entry
|
|
||||||
serviceEntry := &auditLogEntry{
|
|
||||||
UserID: entry.UserId,
|
|
||||||
Action: entry.Action,
|
|
||||||
Resource: entry.Resource,
|
|
||||||
ResourceID: entry.ResourceId,
|
|
||||||
IPAddress: entry.IpAddress,
|
|
||||||
UserAgent: entry.UserAgent,
|
|
||||||
Metadata: entry.Metadata,
|
|
||||||
Timestamp: entry.Timestamp,
|
|
||||||
}
|
|
||||||
|
|
||||||
// Record the audit log
|
|
||||||
if err := s.service.record(ctx, serviceEntry); err != nil {
|
|
||||||
s.logger.Error("Failed to record audit log",
|
|
||||||
zap.Error(err),
|
|
||||||
zap.String("user_id", entry.UserId),
|
|
||||||
zap.String("action", entry.Action),
|
|
||||||
)
|
|
||||||
return nil, status.Errorf(codes.Internal, "failed to record audit log: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return &auditv1.RecordResponse{
|
|
||||||
Success: true,
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Query queries audit logs based on filters.
|
|
||||||
func (s *auditServerImpl) Query(ctx context.Context, req *auditv1.QueryRequest) (*auditv1.QueryResponse, error) {
|
|
||||||
// Convert proto filters to service filters
|
|
||||||
filters := &auditLogFilters{
|
|
||||||
Limit: int(req.Limit),
|
|
||||||
Offset: int(req.Offset),
|
|
||||||
}
|
|
||||||
|
|
||||||
if req.UserId != nil {
|
|
||||||
userID := *req.UserId
|
|
||||||
filters.UserID = &userID
|
|
||||||
}
|
|
||||||
if req.Action != nil {
|
|
||||||
action := *req.Action
|
|
||||||
filters.Action = &action
|
|
||||||
}
|
|
||||||
if req.Resource != nil {
|
|
||||||
resource := *req.Resource
|
|
||||||
filters.Resource = &resource
|
|
||||||
}
|
|
||||||
if req.ResourceId != nil {
|
|
||||||
resourceID := *req.ResourceId
|
|
||||||
filters.ResourceID = &resourceID
|
|
||||||
}
|
|
||||||
if req.StartTime != nil {
|
|
||||||
startTime := *req.StartTime
|
|
||||||
filters.StartTime = &startTime
|
|
||||||
}
|
|
||||||
if req.EndTime != nil {
|
|
||||||
endTime := *req.EndTime
|
|
||||||
filters.EndTime = &endTime
|
|
||||||
}
|
|
||||||
|
|
||||||
// Query audit logs
|
|
||||||
entries, err := s.service.query(ctx, filters)
|
|
||||||
if err != nil {
|
|
||||||
s.logger.Error("Failed to query audit logs",
|
|
||||||
zap.Error(err),
|
|
||||||
)
|
|
||||||
return nil, status.Errorf(codes.Internal, "failed to query audit logs: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Convert service entries to proto entries
|
|
||||||
protoEntries := make([]*auditv1.AuditLogEntry, 0, len(entries))
|
|
||||||
for _, entry := range entries {
|
|
||||||
protoEntries = append(protoEntries, &auditv1.AuditLogEntry{
|
|
||||||
UserId: entry.UserID,
|
|
||||||
Action: entry.Action,
|
|
||||||
Resource: entry.Resource,
|
|
||||||
ResourceId: entry.ResourceID,
|
|
||||||
IpAddress: entry.IPAddress,
|
|
||||||
UserAgent: entry.UserAgent,
|
|
||||||
Metadata: entry.Metadata,
|
|
||||||
Timestamp: entry.Timestamp,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
total := len(protoEntries)
|
|
||||||
var totalInt32 int32
|
|
||||||
if total > math.MaxInt32 {
|
|
||||||
totalInt32 = math.MaxInt32
|
|
||||||
} else {
|
|
||||||
totalInt32 = int32(total)
|
|
||||||
}
|
|
||||||
return &auditv1.QueryResponse{
|
|
||||||
Entries: protoEntries,
|
|
||||||
Total: totalInt32,
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// provideAuditService creates the audit service and gRPC server.
|
|
||||||
func provideAuditService() fx.Option {
|
|
||||||
return fx.Options(
|
|
||||||
// Audit service
|
|
||||||
fx.Provide(func(client *database.Client, log logger.Logger) (*auditService, error) {
|
|
||||||
return &auditService{
|
|
||||||
client: client,
|
|
||||||
logger: log,
|
|
||||||
}, nil
|
|
||||||
}),
|
|
||||||
|
|
||||||
// gRPC server implementation
|
|
||||||
fx.Provide(func(auditService *auditService, log logger.Logger) (*auditServerImpl, error) {
|
|
||||||
zapLogger, _ := zap.NewProduction()
|
|
||||||
return &auditServerImpl{
|
|
||||||
service: auditService,
|
|
||||||
logger: zapLogger,
|
|
||||||
}, nil
|
|
||||||
}),
|
|
||||||
|
|
||||||
// gRPC server wrapper
|
|
||||||
fx.Provide(func(
|
|
||||||
serverImpl *auditServerImpl,
|
|
||||||
cfg config.ConfigProvider,
|
|
||||||
log logger.Logger,
|
|
||||||
) (*grpcServerWrapper, error) {
|
|
||||||
port := cfg.GetInt("services.audit.port")
|
|
||||||
if port == 0 {
|
|
||||||
port = 8084
|
|
||||||
}
|
|
||||||
|
|
||||||
addr := fmt.Sprintf("0.0.0.0:%d", port)
|
|
||||||
listener, err := net.Listen("tcp", addr)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to listen on %s: %w", addr, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
grpcServer := grpc.NewServer()
|
|
||||||
auditv1.RegisterAuditServiceServer(grpcServer, serverImpl)
|
|
||||||
|
|
||||||
// Register health service
|
|
||||||
healthServer := health.NewServer()
|
|
||||||
grpc_health_v1.RegisterHealthServer(grpcServer, healthServer)
|
|
||||||
// Set serving status for the default service (empty string) - this is what Consul checks
|
|
||||||
healthServer.SetServingStatus("", grpc_health_v1.HealthCheckResponse_SERVING)
|
|
||||||
// Also set for the specific service name
|
|
||||||
healthServer.SetServingStatus("audit.v1.AuditService", grpc_health_v1.HealthCheckResponse_SERVING)
|
|
||||||
|
|
||||||
// Register reflection for grpcurl
|
|
||||||
reflection.Register(grpcServer)
|
|
||||||
|
|
||||||
return &grpcServerWrapper{
|
|
||||||
server: grpcServer,
|
|
||||||
listener: listener,
|
|
||||||
port: port,
|
|
||||||
logger: log,
|
|
||||||
}, nil
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -1,228 +0,0 @@
|
|||||||
// Package main provides the entry point for the Audit Service.
|
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
"net"
|
|
||||||
"os"
|
|
||||||
"os/signal"
|
|
||||||
"syscall"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"git.dcentral.systems/toolz/goplt/internal/di"
|
|
||||||
healthpkg "git.dcentral.systems/toolz/goplt/internal/health"
|
|
||||||
"git.dcentral.systems/toolz/goplt/internal/infra/database"
|
|
||||||
"git.dcentral.systems/toolz/goplt/pkg/config"
|
|
||||||
"git.dcentral.systems/toolz/goplt/pkg/logger"
|
|
||||||
"git.dcentral.systems/toolz/goplt/pkg/registry"
|
|
||||||
"go.uber.org/fx"
|
|
||||||
"go.uber.org/zap"
|
|
||||||
"google.golang.org/grpc"
|
|
||||||
)
|
|
||||||
|
|
||||||
// grpcServerWrapper wraps the gRPC server for lifecycle management.
|
|
||||||
type grpcServerWrapper struct {
|
|
||||||
server *grpc.Server
|
|
||||||
listener net.Listener
|
|
||||||
port int
|
|
||||||
logger logger.Logger
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *grpcServerWrapper) Start() error {
|
|
||||||
s.logger.Info("Starting Audit Service gRPC server",
|
|
||||||
zap.Int("port", s.port),
|
|
||||||
zap.String("addr", s.listener.Addr().String()),
|
|
||||||
)
|
|
||||||
|
|
||||||
errChan := make(chan error, 1)
|
|
||||||
go func() {
|
|
||||||
if err := s.server.Serve(s.listener); err != nil {
|
|
||||||
errChan <- err
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
select {
|
|
||||||
case err := <-errChan:
|
|
||||||
return fmt.Errorf("gRPC server failed to start: %w", err)
|
|
||||||
case <-time.After(100 * time.Millisecond):
|
|
||||||
s.logger.Info("Audit Service gRPC server started successfully",
|
|
||||||
zap.Int("port", s.port),
|
|
||||||
)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *grpcServerWrapper) Stop(ctx context.Context) error {
|
|
||||||
s.logger.Info("Stopping Audit Service gRPC server")
|
|
||||||
|
|
||||||
stopped := make(chan struct{})
|
|
||||||
go func() {
|
|
||||||
s.server.GracefulStop()
|
|
||||||
close(stopped)
|
|
||||||
}()
|
|
||||||
|
|
||||||
select {
|
|
||||||
case <-stopped:
|
|
||||||
s.logger.Info("Audit Service gRPC server stopped gracefully")
|
|
||||||
return nil
|
|
||||||
case <-ctx.Done():
|
|
||||||
s.logger.Warn("Audit Service gRPC server stop timeout, forcing stop")
|
|
||||||
s.server.Stop()
|
|
||||||
return ctx.Err()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *grpcServerWrapper) Port() int {
|
|
||||||
return s.port
|
|
||||||
}
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
// Create DI container - services will be created via fx.Provide
|
|
||||||
// Note: CoreModule() is automatically included by NewContainer()
|
|
||||||
container := di.NewContainer(
|
|
||||||
// Database for audit service (audit schema)
|
|
||||||
fx.Provide(func(cfg config.ConfigProvider, log logger.Logger) (*database.Client, error) {
|
|
||||||
dsn := cfg.GetString("database.dsn")
|
|
||||||
if dsn == "" {
|
|
||||||
return nil, fmt.Errorf("database.dsn is required")
|
|
||||||
}
|
|
||||||
client, err := database.NewClientWithSchema(dsn, "audit")
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Run migrations
|
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
|
|
||||||
defer cancel()
|
|
||||||
|
|
||||||
if err := client.Migrate(ctx); err != nil {
|
|
||||||
log.Warn("Failed to run migrations",
|
|
||||||
zap.Error(err),
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
log.Info("Database migrations completed for audit service")
|
|
||||||
}
|
|
||||||
|
|
||||||
return client, nil
|
|
||||||
}),
|
|
||||||
|
|
||||||
// Register database health checker with existing health registry
|
|
||||||
fx.Invoke(func(registry *healthpkg.Registry, db *database.Client) {
|
|
||||||
registry.Register("database", healthpkg.NewDatabaseChecker(db))
|
|
||||||
}),
|
|
||||||
|
|
||||||
// Provide audit service and gRPC server (defined in audit_service_fx.go)
|
|
||||||
provideAuditService(),
|
|
||||||
|
|
||||||
// Lifecycle hooks
|
|
||||||
fx.Invoke(registerLifecycle),
|
|
||||||
)
|
|
||||||
|
|
||||||
// Create root context
|
|
||||||
ctx, cancel := context.WithCancel(context.Background())
|
|
||||||
defer cancel()
|
|
||||||
|
|
||||||
// Handle signals
|
|
||||||
sigChan := make(chan os.Signal, 1)
|
|
||||||
signal.Notify(sigChan, os.Interrupt, syscall.SIGTERM)
|
|
||||||
|
|
||||||
// Start the application
|
|
||||||
if err := container.Start(ctx); err != nil {
|
|
||||||
log := logger.GetGlobalLogger()
|
|
||||||
if log != nil {
|
|
||||||
log.Error("Failed to start Audit Service",
|
|
||||||
logger.Error(err),
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
fmt.Fprintf(os.Stderr, "Failed to start Audit Service: %v\n", err)
|
|
||||||
}
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Wait for interrupt signal
|
|
||||||
<-sigChan
|
|
||||||
fmt.Println("\nShutting down Audit Service...")
|
|
||||||
|
|
||||||
// Create shutdown context with timeout
|
|
||||||
shutdownCtx, shutdownCancel := context.WithTimeout(context.Background(), 30*time.Second)
|
|
||||||
defer shutdownCancel()
|
|
||||||
|
|
||||||
// Stop the application
|
|
||||||
if err := container.Stop(shutdownCtx); err != nil {
|
|
||||||
log := logger.GetGlobalLogger()
|
|
||||||
if log != nil {
|
|
||||||
log.Error("Error during Audit Service shutdown",
|
|
||||||
logger.Error(err),
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
fmt.Fprintf(os.Stderr, "Error during shutdown: %v\n", err)
|
|
||||||
}
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
fmt.Println("Audit Service stopped successfully")
|
|
||||||
}
|
|
||||||
|
|
||||||
// registerLifecycle registers lifecycle hooks for the service.
|
|
||||||
func registerLifecycle(
|
|
||||||
lc fx.Lifecycle,
|
|
||||||
grpcServer *grpcServerWrapper,
|
|
||||||
serviceRegistry registry.ServiceRegistry,
|
|
||||||
cfg config.ConfigProvider,
|
|
||||||
log logger.Logger,
|
|
||||||
) {
|
|
||||||
lc.Append(fx.Hook{
|
|
||||||
OnStart: func(ctx context.Context) error {
|
|
||||||
// Start gRPC server
|
|
||||||
if err := grpcServer.Start(); err != nil {
|
|
||||||
return fmt.Errorf("failed to start gRPC server: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Register with service registry
|
|
||||||
serviceID := fmt.Sprintf("audit-service-%d", time.Now().Unix())
|
|
||||||
// In Docker, always use the Docker service name for health checks
|
|
||||||
// Consul (also in Docker) needs to reach the service via Docker DNS
|
|
||||||
host := cfg.GetString("services.audit.host")
|
|
||||||
if os.Getenv("ENVIRONMENT") == "production" || os.Getenv("DOCKER") == "true" {
|
|
||||||
host = "audit-service" // Docker service name - required for Consul health checks
|
|
||||||
} else if host == "" {
|
|
||||||
host = "localhost" // Local development
|
|
||||||
}
|
|
||||||
port := grpcServer.Port()
|
|
||||||
|
|
||||||
instance := ®istry.ServiceInstance{
|
|
||||||
ID: serviceID,
|
|
||||||
Name: "audit-service",
|
|
||||||
Address: host,
|
|
||||||
Port: port,
|
|
||||||
Tags: []string{"grpc", "audit"},
|
|
||||||
Metadata: map[string]string{
|
|
||||||
"version": "1.0.0",
|
|
||||||
"protocol": "grpc",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := serviceRegistry.Register(ctx, instance); err != nil {
|
|
||||||
log.Warn("Failed to register with service registry",
|
|
||||||
zap.Error(err),
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
log.Info("Registered Audit Service with service registry",
|
|
||||||
zap.String("service_id", serviceID),
|
|
||||||
zap.String("name", instance.Name),
|
|
||||||
zap.Int("port", port),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
},
|
|
||||||
OnStop: func(ctx context.Context) error {
|
|
||||||
// Stop gRPC server
|
|
||||||
if err := grpcServer.Stop(ctx); err != nil {
|
|
||||||
return fmt.Errorf("failed to stop gRPC server: %w", err)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
},
|
|
||||||
})
|
|
||||||
}
|
|
||||||
@@ -1,36 +0,0 @@
|
|||||||
# Build stage
|
|
||||||
FROM golang:1.25-alpine AS builder
|
|
||||||
|
|
||||||
WORKDIR /build
|
|
||||||
|
|
||||||
# Install build dependencies
|
|
||||||
RUN apk add --no-cache git make
|
|
||||||
|
|
||||||
# Copy go mod files
|
|
||||||
COPY go.mod go.sum ./
|
|
||||||
RUN go mod download
|
|
||||||
|
|
||||||
# Copy source code
|
|
||||||
COPY . .
|
|
||||||
|
|
||||||
# Build the service
|
|
||||||
WORKDIR /build/cmd/auth-service
|
|
||||||
RUN CGO_ENABLED=0 GOOS=linux go build -o auth-service -a -installsuffix cgo .
|
|
||||||
|
|
||||||
# Runtime stage
|
|
||||||
FROM alpine:latest
|
|
||||||
|
|
||||||
RUN apk --no-cache add ca-certificates tzdata
|
|
||||||
|
|
||||||
WORKDIR /app
|
|
||||||
|
|
||||||
# Copy binary from builder
|
|
||||||
COPY --from=builder /build/cmd/auth-service/auth-service .
|
|
||||||
|
|
||||||
# Copy config files
|
|
||||||
COPY --from=builder /build/config ./config
|
|
||||||
|
|
||||||
EXPOSE 8081
|
|
||||||
|
|
||||||
CMD ["./auth-service"]
|
|
||||||
|
|
||||||
@@ -1,425 +0,0 @@
|
|||||||
// Package main provides FX providers for Auth Service.
|
|
||||||
// This file creates the service inline to avoid importing internal packages.
|
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"crypto/rand"
|
|
||||||
"crypto/sha256"
|
|
||||||
"encoding/hex"
|
|
||||||
"fmt"
|
|
||||||
"net"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
authv1 "git.dcentral.systems/toolz/goplt/api/proto/generated/auth/v1"
|
|
||||||
"git.dcentral.systems/toolz/goplt/internal/ent/refreshtoken"
|
|
||||||
"git.dcentral.systems/toolz/goplt/internal/infra/database"
|
|
||||||
"git.dcentral.systems/toolz/goplt/pkg/config"
|
|
||||||
"git.dcentral.systems/toolz/goplt/pkg/logger"
|
|
||||||
"git.dcentral.systems/toolz/goplt/pkg/services"
|
|
||||||
"github.com/golang-jwt/jwt/v5"
|
|
||||||
"go.uber.org/fx"
|
|
||||||
"go.uber.org/zap"
|
|
||||||
"google.golang.org/grpc"
|
|
||||||
"google.golang.org/grpc/codes"
|
|
||||||
"google.golang.org/grpc/health"
|
|
||||||
"google.golang.org/grpc/health/grpc_health_v1"
|
|
||||||
"google.golang.org/grpc/reflection"
|
|
||||||
"google.golang.org/grpc/status"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
accessTokenLifetime = 15 * time.Minute
|
|
||||||
refreshTokenLifetime = 7 * 24 * time.Hour
|
|
||||||
)
|
|
||||||
|
|
||||||
// authService provides authentication functionality.
|
|
||||||
type authService struct {
|
|
||||||
client *database.Client
|
|
||||||
logger logger.Logger
|
|
||||||
identityClient services.IdentityServiceClient
|
|
||||||
authzClient services.AuthzServiceClient
|
|
||||||
jwtSecret []byte
|
|
||||||
accessTokenExpiry time.Duration
|
|
||||||
refreshTokenExpiry time.Duration
|
|
||||||
}
|
|
||||||
|
|
||||||
// hashToken hashes a token using SHA256.
|
|
||||||
func hashToken(token string) string {
|
|
||||||
h := sha256.Sum256([]byte(token))
|
|
||||||
return hex.EncodeToString(h[:])
|
|
||||||
}
|
|
||||||
|
|
||||||
// generateRefreshToken generates a random refresh token.
|
|
||||||
func generateRefreshToken() (string, error) {
|
|
||||||
b := make([]byte, 32)
|
|
||||||
if _, err := rand.Read(b); err != nil {
|
|
||||||
return "", fmt.Errorf("failed to generate token: %w", err)
|
|
||||||
}
|
|
||||||
return hex.EncodeToString(b), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// generateAccessToken generates a JWT access token.
|
|
||||||
func (s *authService) generateAccessToken(userID, email string, roles []string) (string, int64, error) {
|
|
||||||
expiresAt := time.Now().Add(s.accessTokenExpiry)
|
|
||||||
claims := jwt.MapClaims{
|
|
||||||
"sub": userID,
|
|
||||||
"email": email,
|
|
||||||
"roles": roles,
|
|
||||||
"exp": expiresAt.Unix(),
|
|
||||||
"iat": time.Now().Unix(),
|
|
||||||
"token_type": "access",
|
|
||||||
}
|
|
||||||
|
|
||||||
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
|
|
||||||
tokenString, err := token.SignedString(s.jwtSecret)
|
|
||||||
if err != nil {
|
|
||||||
return "", 0, fmt.Errorf("failed to sign token: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return tokenString, int64(s.accessTokenExpiry.Seconds()), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// generateRefreshToken generates a refresh token and stores it in the database.
|
|
||||||
func (s *authService) generateRefreshToken(ctx context.Context, userID string) (string, error) {
|
|
||||||
token, err := generateRefreshToken()
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
tokenHash := hashToken(token)
|
|
||||||
expiresAt := time.Now().Add(s.refreshTokenExpiry)
|
|
||||||
|
|
||||||
// Store refresh token in database
|
|
||||||
_, err = s.client.RefreshToken.Create().
|
|
||||||
SetID(fmt.Sprintf("%d-%d", time.Now().Unix(), time.Now().UnixNano()%1000000)).
|
|
||||||
SetUserID(userID).
|
|
||||||
SetTokenHash(tokenHash).
|
|
||||||
SetExpiresAt(expiresAt).
|
|
||||||
Save(ctx)
|
|
||||||
if err != nil {
|
|
||||||
return "", fmt.Errorf("failed to store refresh token: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return token, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// validateAccessToken validates a JWT access token.
|
|
||||||
func (s *authService) validateAccessToken(tokenString string) (*jwt.Token, jwt.MapClaims, error) {
|
|
||||||
token, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) {
|
|
||||||
if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
|
|
||||||
return nil, fmt.Errorf("unexpected signing method: %v", token.Header["alg"])
|
|
||||||
}
|
|
||||||
return s.jwtSecret, nil
|
|
||||||
})
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, fmt.Errorf("failed to parse token: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if !token.Valid {
|
|
||||||
return nil, nil, fmt.Errorf("invalid token")
|
|
||||||
}
|
|
||||||
|
|
||||||
claims, ok := token.Claims.(jwt.MapClaims)
|
|
||||||
if !ok {
|
|
||||||
return nil, nil, fmt.Errorf("invalid token claims")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check expiration
|
|
||||||
if exp, ok := claims["exp"].(float64); ok {
|
|
||||||
if time.Now().Unix() > int64(exp) {
|
|
||||||
return nil, nil, fmt.Errorf("token expired")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return token, claims, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// validateRefreshToken validates a refresh token.
|
|
||||||
func (s *authService) validateRefreshToken(ctx context.Context, tokenString string) (string, error) {
|
|
||||||
tokenHash := hashToken(tokenString)
|
|
||||||
|
|
||||||
// Find refresh token by hash
|
|
||||||
rt, err := s.client.RefreshToken.Query().
|
|
||||||
Where(refreshtoken.TokenHash(tokenHash)).
|
|
||||||
Only(ctx)
|
|
||||||
if err != nil {
|
|
||||||
return "", fmt.Errorf("invalid refresh token")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if token has expired
|
|
||||||
if rt.ExpiresAt.Before(time.Now()) {
|
|
||||||
// Delete expired token
|
|
||||||
_ = s.client.RefreshToken.DeleteOneID(rt.ID).Exec(ctx)
|
|
||||||
return "", fmt.Errorf("refresh token expired")
|
|
||||||
}
|
|
||||||
|
|
||||||
return rt.UserID, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// revokeRefreshToken revokes a refresh token.
|
|
||||||
func (s *authService) revokeRefreshToken(ctx context.Context, tokenString string) error {
|
|
||||||
tokenHash := hashToken(tokenString)
|
|
||||||
|
|
||||||
// Find and delete refresh token
|
|
||||||
rt, err := s.client.RefreshToken.Query().
|
|
||||||
Where(refreshtoken.TokenHash(tokenHash)).
|
|
||||||
Only(ctx)
|
|
||||||
if err != nil {
|
|
||||||
// Token not found, consider it already revoked
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return s.client.RefreshToken.DeleteOneID(rt.ID).Exec(ctx)
|
|
||||||
}
|
|
||||||
|
|
||||||
// login authenticates a user and returns tokens.
|
|
||||||
func (s *authService) login(ctx context.Context, email, password string) (*authv1.LoginResponse, error) {
|
|
||||||
// Verify credentials with Identity Service
|
|
||||||
user, err := s.identityClient.VerifyPassword(ctx, email, password)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("invalid credentials")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get user roles from Authz Service
|
|
||||||
roles := []string{}
|
|
||||||
if s.authzClient != nil {
|
|
||||||
userRoles, err := s.authzClient.GetUserRoles(ctx, user.ID)
|
|
||||||
if err != nil {
|
|
||||||
s.logger.Warn("Failed to get user roles",
|
|
||||||
zap.String("user_id", user.ID),
|
|
||||||
zap.Error(err),
|
|
||||||
)
|
|
||||||
// Continue without roles rather than failing login
|
|
||||||
} else {
|
|
||||||
for _, role := range userRoles {
|
|
||||||
roles = append(roles, role.Name)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Generate tokens
|
|
||||||
accessToken, expiresIn, err := s.generateAccessToken(user.ID, user.Email, roles)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to generate access token: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
refreshToken, err := s.generateRefreshToken(ctx, user.ID)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to generate refresh token: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return &authv1.LoginResponse{
|
|
||||||
AccessToken: accessToken,
|
|
||||||
RefreshToken: refreshToken,
|
|
||||||
ExpiresIn: expiresIn,
|
|
||||||
TokenType: "Bearer",
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// refreshToken refreshes an access token.
|
|
||||||
func (s *authService) refreshToken(ctx context.Context, refreshTokenString string) (*authv1.RefreshTokenResponse, error) {
|
|
||||||
// Validate refresh token
|
|
||||||
userID, err := s.validateRefreshToken(ctx, refreshTokenString)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get user from Identity Service
|
|
||||||
user, err := s.identityClient.GetUser(ctx, userID)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("user not found")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get user roles from Authz Service
|
|
||||||
roles := []string{}
|
|
||||||
if s.authzClient != nil {
|
|
||||||
userRoles, err := s.authzClient.GetUserRoles(ctx, user.ID)
|
|
||||||
if err != nil {
|
|
||||||
s.logger.Warn("Failed to get user roles",
|
|
||||||
zap.String("user_id", user.ID),
|
|
||||||
zap.Error(err),
|
|
||||||
)
|
|
||||||
// Continue without roles rather than failing refresh
|
|
||||||
} else {
|
|
||||||
for _, role := range userRoles {
|
|
||||||
roles = append(roles, role.Name)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Generate new tokens
|
|
||||||
accessToken, expiresIn, err := s.generateAccessToken(user.ID, user.Email, roles)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to generate access token: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Generate new refresh token (rotate)
|
|
||||||
newRefreshToken, err := s.generateRefreshToken(ctx, user.ID)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to generate refresh token: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Revoke old refresh token
|
|
||||||
_ = s.revokeRefreshToken(ctx, refreshTokenString)
|
|
||||||
|
|
||||||
return &authv1.RefreshTokenResponse{
|
|
||||||
AccessToken: accessToken,
|
|
||||||
RefreshToken: newRefreshToken,
|
|
||||||
ExpiresIn: expiresIn,
|
|
||||||
TokenType: "Bearer",
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// validateToken validates a JWT token.
|
|
||||||
func (s *authService) validateToken(tokenString string) (*authv1.ValidateTokenResponse, error) {
|
|
||||||
_, claims, err := s.validateAccessToken(tokenString)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
userID, _ := claims["sub"].(string)
|
|
||||||
email, _ := claims["email"].(string)
|
|
||||||
exp, _ := claims["exp"].(float64)
|
|
||||||
|
|
||||||
roles := []string{}
|
|
||||||
if rolesClaim, ok := claims["roles"].([]interface{}); ok {
|
|
||||||
for _, r := range rolesClaim {
|
|
||||||
if role, ok := r.(string); ok {
|
|
||||||
roles = append(roles, role)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return &authv1.ValidateTokenResponse{
|
|
||||||
UserId: userID,
|
|
||||||
Email: email,
|
|
||||||
Roles: roles,
|
|
||||||
ExpiresAt: int64(exp),
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// logout invalidates a refresh token.
|
|
||||||
func (s *authService) logout(ctx context.Context, refreshTokenString string) error {
|
|
||||||
return s.revokeRefreshToken(ctx, refreshTokenString)
|
|
||||||
}
|
|
||||||
|
|
||||||
// authServerImpl implements the AuthService gRPC server.
|
|
||||||
type authServerImpl struct {
|
|
||||||
authv1.UnimplementedAuthServiceServer
|
|
||||||
service *authService
|
|
||||||
logger *zap.Logger
|
|
||||||
}
|
|
||||||
|
|
||||||
// Login authenticates a user and returns tokens.
|
|
||||||
func (s *authServerImpl) Login(ctx context.Context, req *authv1.LoginRequest) (*authv1.LoginResponse, error) {
|
|
||||||
resp, err := s.service.login(ctx, req.Email, req.Password)
|
|
||||||
if err != nil {
|
|
||||||
return nil, status.Errorf(codes.Unauthenticated, "login failed: %v", err)
|
|
||||||
}
|
|
||||||
return resp, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// RefreshToken refreshes an access token.
|
|
||||||
func (s *authServerImpl) RefreshToken(ctx context.Context, req *authv1.RefreshTokenRequest) (*authv1.RefreshTokenResponse, error) {
|
|
||||||
resp, err := s.service.refreshToken(ctx, req.RefreshToken)
|
|
||||||
if err != nil {
|
|
||||||
return nil, status.Errorf(codes.Unauthenticated, "refresh failed: %v", err)
|
|
||||||
}
|
|
||||||
return resp, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// ValidateToken validates a JWT token.
|
|
||||||
func (s *authServerImpl) ValidateToken(ctx context.Context, req *authv1.ValidateTokenRequest) (*authv1.ValidateTokenResponse, error) {
|
|
||||||
resp, err := s.service.validateToken(req.Token)
|
|
||||||
if err != nil {
|
|
||||||
return nil, status.Errorf(codes.Unauthenticated, "validation failed: %v", err)
|
|
||||||
}
|
|
||||||
return resp, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Logout invalidates a refresh token.
|
|
||||||
func (s *authServerImpl) Logout(ctx context.Context, req *authv1.LogoutRequest) (*authv1.LogoutResponse, error) {
|
|
||||||
if err := s.service.logout(ctx, req.RefreshToken); err != nil {
|
|
||||||
return nil, status.Errorf(codes.Internal, "logout failed: %v", err)
|
|
||||||
}
|
|
||||||
return &authv1.LogoutResponse{Success: true}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// provideAuthService creates the auth service and gRPC server.
|
|
||||||
func provideAuthService() fx.Option {
|
|
||||||
return fx.Options(
|
|
||||||
// Auth service
|
|
||||||
fx.Provide(func(
|
|
||||||
client *database.Client,
|
|
||||||
log logger.Logger,
|
|
||||||
identityClient services.IdentityServiceClient,
|
|
||||||
authzClient services.AuthzServiceClient,
|
|
||||||
cfg config.ConfigProvider,
|
|
||||||
) (*authService, error) {
|
|
||||||
jwtSecret := cfg.GetString("auth.jwt_secret")
|
|
||||||
if jwtSecret == "" {
|
|
||||||
return nil, fmt.Errorf("auth.jwt_secret is required in configuration")
|
|
||||||
}
|
|
||||||
|
|
||||||
return &authService{
|
|
||||||
client: client,
|
|
||||||
logger: log,
|
|
||||||
identityClient: identityClient,
|
|
||||||
authzClient: authzClient,
|
|
||||||
jwtSecret: []byte(jwtSecret),
|
|
||||||
accessTokenExpiry: accessTokenLifetime,
|
|
||||||
refreshTokenExpiry: refreshTokenLifetime,
|
|
||||||
}, nil
|
|
||||||
}),
|
|
||||||
|
|
||||||
// gRPC server implementation
|
|
||||||
fx.Provide(func(authService *authService, log logger.Logger) (*authServerImpl, error) {
|
|
||||||
zapLogger, _ := zap.NewProduction()
|
|
||||||
return &authServerImpl{
|
|
||||||
service: authService,
|
|
||||||
logger: zapLogger,
|
|
||||||
}, nil
|
|
||||||
}),
|
|
||||||
|
|
||||||
// gRPC server wrapper
|
|
||||||
fx.Provide(func(
|
|
||||||
serverImpl *authServerImpl,
|
|
||||||
cfg config.ConfigProvider,
|
|
||||||
log logger.Logger,
|
|
||||||
) (*grpcServerWrapper, error) {
|
|
||||||
port := cfg.GetInt("services.auth.port")
|
|
||||||
if port == 0 {
|
|
||||||
port = 8081
|
|
||||||
}
|
|
||||||
|
|
||||||
addr := fmt.Sprintf("0.0.0.0:%d", port)
|
|
||||||
listener, err := net.Listen("tcp", addr)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to listen on %s: %w", addr, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
grpcServer := grpc.NewServer()
|
|
||||||
authv1.RegisterAuthServiceServer(grpcServer, serverImpl)
|
|
||||||
|
|
||||||
// Register health service
|
|
||||||
healthServer := health.NewServer()
|
|
||||||
grpc_health_v1.RegisterHealthServer(grpcServer, healthServer)
|
|
||||||
// Set serving status for the default service (empty string) - this is what Consul checks
|
|
||||||
healthServer.SetServingStatus("", grpc_health_v1.HealthCheckResponse_SERVING)
|
|
||||||
// Also set for the specific service name
|
|
||||||
healthServer.SetServingStatus("auth.v1.AuthService", grpc_health_v1.HealthCheckResponse_SERVING)
|
|
||||||
|
|
||||||
// Register reflection for grpcurl
|
|
||||||
reflection.Register(grpcServer)
|
|
||||||
|
|
||||||
return &grpcServerWrapper{
|
|
||||||
server: grpcServer,
|
|
||||||
listener: listener,
|
|
||||||
port: port,
|
|
||||||
logger: log,
|
|
||||||
}, nil
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -1,240 +0,0 @@
|
|||||||
// Package main provides the entry point for the Auth Service.
|
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
"net"
|
|
||||||
"os"
|
|
||||||
"os/signal"
|
|
||||||
"syscall"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"git.dcentral.systems/toolz/goplt/internal/client"
|
|
||||||
"git.dcentral.systems/toolz/goplt/internal/di"
|
|
||||||
healthpkg "git.dcentral.systems/toolz/goplt/internal/health"
|
|
||||||
"git.dcentral.systems/toolz/goplt/internal/infra/database"
|
|
||||||
"git.dcentral.systems/toolz/goplt/pkg/config"
|
|
||||||
"git.dcentral.systems/toolz/goplt/pkg/logger"
|
|
||||||
"git.dcentral.systems/toolz/goplt/pkg/registry"
|
|
||||||
"git.dcentral.systems/toolz/goplt/pkg/services"
|
|
||||||
"go.uber.org/fx"
|
|
||||||
"go.uber.org/zap"
|
|
||||||
"google.golang.org/grpc"
|
|
||||||
)
|
|
||||||
|
|
||||||
// grpcServerWrapper wraps the gRPC server for lifecycle management.
|
|
||||||
type grpcServerWrapper struct {
|
|
||||||
server *grpc.Server
|
|
||||||
listener net.Listener
|
|
||||||
port int
|
|
||||||
logger logger.Logger
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *grpcServerWrapper) Start() error {
|
|
||||||
s.logger.Info("Starting Auth Service gRPC server",
|
|
||||||
zap.Int("port", s.port),
|
|
||||||
zap.String("addr", s.listener.Addr().String()),
|
|
||||||
)
|
|
||||||
|
|
||||||
errChan := make(chan error, 1)
|
|
||||||
go func() {
|
|
||||||
if err := s.server.Serve(s.listener); err != nil {
|
|
||||||
errChan <- err
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
select {
|
|
||||||
case err := <-errChan:
|
|
||||||
return fmt.Errorf("gRPC server failed to start: %w", err)
|
|
||||||
case <-time.After(100 * time.Millisecond):
|
|
||||||
s.logger.Info("Auth Service gRPC server started successfully",
|
|
||||||
zap.Int("port", s.port),
|
|
||||||
)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *grpcServerWrapper) Stop(ctx context.Context) error {
|
|
||||||
s.logger.Info("Stopping Auth Service gRPC server")
|
|
||||||
|
|
||||||
stopped := make(chan struct{})
|
|
||||||
go func() {
|
|
||||||
s.server.GracefulStop()
|
|
||||||
close(stopped)
|
|
||||||
}()
|
|
||||||
|
|
||||||
select {
|
|
||||||
case <-stopped:
|
|
||||||
s.logger.Info("Auth Service gRPC server stopped gracefully")
|
|
||||||
return nil
|
|
||||||
case <-ctx.Done():
|
|
||||||
s.logger.Warn("Auth Service gRPC server stop timeout, forcing stop")
|
|
||||||
s.server.Stop()
|
|
||||||
return ctx.Err()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *grpcServerWrapper) Port() int {
|
|
||||||
return s.port
|
|
||||||
}
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
// Create DI container
|
|
||||||
// Note: CoreModule() is automatically included by NewContainer()
|
|
||||||
container := di.NewContainer(
|
|
||||||
// Database for auth service (auth schema)
|
|
||||||
fx.Provide(func(cfg config.ConfigProvider, log logger.Logger) (*database.Client, error) {
|
|
||||||
dsn := cfg.GetString("database.dsn")
|
|
||||||
if dsn == "" {
|
|
||||||
return nil, fmt.Errorf("database.dsn is required")
|
|
||||||
}
|
|
||||||
client, err := database.NewClientWithSchema(dsn, "auth")
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Run migrations
|
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
|
|
||||||
defer cancel()
|
|
||||||
|
|
||||||
if err := client.Migrate(ctx); err != nil {
|
|
||||||
log.Warn("Failed to run migrations",
|
|
||||||
zap.Error(err),
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
log.Info("Database migrations completed for auth service")
|
|
||||||
}
|
|
||||||
|
|
||||||
return client, nil
|
|
||||||
}),
|
|
||||||
|
|
||||||
// Register database health checker with existing health registry
|
|
||||||
fx.Invoke(func(registry *healthpkg.Registry, db *database.Client) {
|
|
||||||
registry.Register("database", healthpkg.NewDatabaseChecker(db))
|
|
||||||
}),
|
|
||||||
|
|
||||||
// Identity Service client
|
|
||||||
fx.Provide(func(factory *client.ServiceClientFactory) (services.IdentityServiceClient, error) {
|
|
||||||
return factory.GetIdentityClient()
|
|
||||||
}),
|
|
||||||
|
|
||||||
// Authz Service client
|
|
||||||
fx.Provide(func(factory *client.ServiceClientFactory) (services.AuthzServiceClient, error) {
|
|
||||||
return factory.GetAuthzClient()
|
|
||||||
}),
|
|
||||||
|
|
||||||
// Provide auth service and gRPC server (defined in auth_service_fx.go)
|
|
||||||
provideAuthService(),
|
|
||||||
|
|
||||||
// Lifecycle hooks
|
|
||||||
fx.Invoke(registerLifecycle),
|
|
||||||
)
|
|
||||||
|
|
||||||
// Create root context
|
|
||||||
ctx, cancel := context.WithCancel(context.Background())
|
|
||||||
defer cancel()
|
|
||||||
|
|
||||||
// Handle signals
|
|
||||||
sigChan := make(chan os.Signal, 1)
|
|
||||||
signal.Notify(sigChan, os.Interrupt, syscall.SIGTERM)
|
|
||||||
|
|
||||||
// Start the application
|
|
||||||
if err := container.Start(ctx); err != nil {
|
|
||||||
log := logger.GetGlobalLogger()
|
|
||||||
if log != nil {
|
|
||||||
log.Error("Failed to start Auth Service",
|
|
||||||
logger.Error(err),
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
fmt.Fprintf(os.Stderr, "Failed to start Auth Service: %v\n", err)
|
|
||||||
}
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Wait for interrupt signal
|
|
||||||
<-sigChan
|
|
||||||
fmt.Println("\nShutting down Auth Service...")
|
|
||||||
|
|
||||||
// Create shutdown context with timeout
|
|
||||||
shutdownCtx, shutdownCancel := context.WithTimeout(context.Background(), 30*time.Second)
|
|
||||||
defer shutdownCancel()
|
|
||||||
|
|
||||||
// Stop the application
|
|
||||||
if err := container.Stop(shutdownCtx); err != nil {
|
|
||||||
log := logger.GetGlobalLogger()
|
|
||||||
if log != nil {
|
|
||||||
log.Error("Error during Auth Service shutdown",
|
|
||||||
logger.Error(err),
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
fmt.Fprintf(os.Stderr, "Error during shutdown: %v\n", err)
|
|
||||||
}
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
fmt.Println("Auth Service stopped successfully")
|
|
||||||
}
|
|
||||||
|
|
||||||
// registerLifecycle registers lifecycle hooks for the service.
|
|
||||||
func registerLifecycle(
|
|
||||||
lc fx.Lifecycle,
|
|
||||||
grpcServer *grpcServerWrapper,
|
|
||||||
serviceRegistry registry.ServiceRegistry,
|
|
||||||
cfg config.ConfigProvider,
|
|
||||||
log logger.Logger,
|
|
||||||
) {
|
|
||||||
lc.Append(fx.Hook{
|
|
||||||
OnStart: func(ctx context.Context) error {
|
|
||||||
// Start gRPC server
|
|
||||||
if err := grpcServer.Start(); err != nil {
|
|
||||||
return fmt.Errorf("failed to start gRPC server: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Register with service registry
|
|
||||||
serviceID := fmt.Sprintf("auth-service-%d", time.Now().Unix())
|
|
||||||
// In Docker, always use the Docker service name for health checks
|
|
||||||
// Consul (also in Docker) needs to reach the service via Docker DNS
|
|
||||||
host := cfg.GetString("services.auth.host")
|
|
||||||
if os.Getenv("ENVIRONMENT") == "production" || os.Getenv("DOCKER") == "true" {
|
|
||||||
host = "auth-service" // Docker service name - required for Consul health checks
|
|
||||||
} else if host == "" {
|
|
||||||
host = "localhost" // Local development
|
|
||||||
}
|
|
||||||
port := grpcServer.Port()
|
|
||||||
|
|
||||||
instance := ®istry.ServiceInstance{
|
|
||||||
ID: serviceID,
|
|
||||||
Name: "auth-service",
|
|
||||||
Address: host,
|
|
||||||
Port: port,
|
|
||||||
Tags: []string{"grpc", "auth"},
|
|
||||||
Metadata: map[string]string{
|
|
||||||
"version": "1.0.0",
|
|
||||||
"protocol": "grpc",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := serviceRegistry.Register(ctx, instance); err != nil {
|
|
||||||
log.Warn("Failed to register with service registry",
|
|
||||||
zap.Error(err),
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
log.Info("Registered Auth Service with service registry",
|
|
||||||
zap.String("service_id", serviceID),
|
|
||||||
zap.String("name", instance.Name),
|
|
||||||
zap.Int("port", port),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
},
|
|
||||||
OnStop: func(ctx context.Context) error {
|
|
||||||
// Stop gRPC server
|
|
||||||
if err := grpcServer.Stop(ctx); err != nil {
|
|
||||||
return fmt.Errorf("failed to stop gRPC server: %w", err)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
},
|
|
||||||
})
|
|
||||||
}
|
|
||||||
@@ -1,36 +0,0 @@
|
|||||||
# Build stage
|
|
||||||
FROM golang:1.25-alpine AS builder
|
|
||||||
|
|
||||||
WORKDIR /build
|
|
||||||
|
|
||||||
# Install build dependencies
|
|
||||||
RUN apk add --no-cache git make
|
|
||||||
|
|
||||||
# Copy go mod files
|
|
||||||
COPY go.mod go.sum ./
|
|
||||||
RUN go mod download
|
|
||||||
|
|
||||||
# Copy source code
|
|
||||||
COPY . .
|
|
||||||
|
|
||||||
# Build the service
|
|
||||||
WORKDIR /build/cmd/authz-service
|
|
||||||
RUN CGO_ENABLED=0 GOOS=linux go build -o authz-service -a -installsuffix cgo .
|
|
||||||
|
|
||||||
# Runtime stage
|
|
||||||
FROM alpine:latest
|
|
||||||
|
|
||||||
RUN apk --no-cache add ca-certificates tzdata
|
|
||||||
|
|
||||||
WORKDIR /app
|
|
||||||
|
|
||||||
# Copy binary from builder
|
|
||||||
COPY --from=builder /build/cmd/authz-service/authz-service .
|
|
||||||
|
|
||||||
# Copy config files
|
|
||||||
COPY --from=builder /build/config ./config
|
|
||||||
|
|
||||||
EXPOSE 8083
|
|
||||||
|
|
||||||
CMD ["./authz-service"]
|
|
||||||
|
|
||||||
@@ -1,290 +0,0 @@
|
|||||||
// Package main provides FX providers for Authz Service.
|
|
||||||
// This file creates the service inline to avoid importing internal packages.
|
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
"net"
|
|
||||||
|
|
||||||
authzv1 "git.dcentral.systems/toolz/goplt/api/proto/generated/authz/v1"
|
|
||||||
"git.dcentral.systems/toolz/goplt/internal/ent"
|
|
||||||
"git.dcentral.systems/toolz/goplt/internal/ent/userrole"
|
|
||||||
"git.dcentral.systems/toolz/goplt/internal/infra/database"
|
|
||||||
"git.dcentral.systems/toolz/goplt/pkg/config"
|
|
||||||
"git.dcentral.systems/toolz/goplt/pkg/logger"
|
|
||||||
"go.uber.org/fx"
|
|
||||||
"go.uber.org/zap"
|
|
||||||
"google.golang.org/grpc"
|
|
||||||
"google.golang.org/grpc/codes"
|
|
||||||
"google.golang.org/grpc/health"
|
|
||||||
"google.golang.org/grpc/health/grpc_health_v1"
|
|
||||||
"google.golang.org/grpc/reflection"
|
|
||||||
"google.golang.org/grpc/status"
|
|
||||||
)
|
|
||||||
|
|
||||||
// authzService provides authorization functionality.
|
|
||||||
type authzService struct {
|
|
||||||
client *database.Client
|
|
||||||
logger logger.Logger
|
|
||||||
}
|
|
||||||
|
|
||||||
// hasPermission checks if a user has a specific permission.
|
|
||||||
func (s *authzService) hasPermission(ctx context.Context, userID, permCode string) (bool, error) {
|
|
||||||
// Get user's roles
|
|
||||||
userRoles, err := s.client.UserRole.Query().
|
|
||||||
Where(userrole.UserID(userID)).
|
|
||||||
WithRole(func(rq *ent.RoleQuery) {
|
|
||||||
rq.WithRolePermissions(func(rpq *ent.RolePermissionQuery) {
|
|
||||||
rpq.WithPermission()
|
|
||||||
})
|
|
||||||
}).
|
|
||||||
All(ctx)
|
|
||||||
if err != nil {
|
|
||||||
return false, fmt.Errorf("failed to get user roles: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if any role has the permission
|
|
||||||
for _, ur := range userRoles {
|
|
||||||
role := ur.Edges.Role
|
|
||||||
if role == nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
rolePerms := role.Edges.RolePermissions
|
|
||||||
for _, rp := range rolePerms {
|
|
||||||
perm := rp.Edges.Permission
|
|
||||||
if perm != nil && perm.Name == permCode {
|
|
||||||
return true, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return false, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// getUserPermissions returns all permissions for a user.
|
|
||||||
func (s *authzService) getUserPermissions(ctx context.Context, userID string) ([]*ent.Permission, error) {
|
|
||||||
// Get user's roles
|
|
||||||
userRoles, err := s.client.UserRole.Query().
|
|
||||||
Where(userrole.UserID(userID)).
|
|
||||||
WithRole(func(rq *ent.RoleQuery) {
|
|
||||||
rq.WithRolePermissions(func(rpq *ent.RolePermissionQuery) {
|
|
||||||
rpq.WithPermission()
|
|
||||||
})
|
|
||||||
}).
|
|
||||||
All(ctx)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to get user roles: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Collect unique permissions
|
|
||||||
permMap := make(map[string]*ent.Permission)
|
|
||||||
for _, ur := range userRoles {
|
|
||||||
role := ur.Edges.Role
|
|
||||||
if role == nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
rolePerms := role.Edges.RolePermissions
|
|
||||||
for _, rp := range rolePerms {
|
|
||||||
perm := rp.Edges.Permission
|
|
||||||
if perm != nil {
|
|
||||||
permMap[perm.ID] = perm
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Convert map to slice
|
|
||||||
permissions := make([]*ent.Permission, 0, len(permMap))
|
|
||||||
for _, perm := range permMap {
|
|
||||||
permissions = append(permissions, perm)
|
|
||||||
}
|
|
||||||
|
|
||||||
return permissions, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// getUserRoles returns all roles for a user.
|
|
||||||
func (s *authzService) getUserRoles(ctx context.Context, userID string) ([]*ent.Role, error) {
|
|
||||||
userRoles, err := s.client.UserRole.Query().
|
|
||||||
Where(userrole.UserID(userID)).
|
|
||||||
WithRole(func(rq *ent.RoleQuery) {
|
|
||||||
rq.WithRolePermissions(func(rpq *ent.RolePermissionQuery) {
|
|
||||||
rpq.WithPermission()
|
|
||||||
})
|
|
||||||
}).
|
|
||||||
All(ctx)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to get user roles: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
roles := make([]*ent.Role, 0, len(userRoles))
|
|
||||||
for _, ur := range userRoles {
|
|
||||||
if ur.Edges.Role != nil {
|
|
||||||
roles = append(roles, ur.Edges.Role)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return roles, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// authorize checks if a user has a specific permission.
|
|
||||||
func (s *authzService) authorize(ctx context.Context, userID, permCode string) (bool, string, error) {
|
|
||||||
hasPerm, err := s.hasPermission(ctx, userID, permCode)
|
|
||||||
if err != nil {
|
|
||||||
return false, "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
if !hasPerm {
|
|
||||||
return false, fmt.Sprintf("user %s does not have permission %s", userID, permCode), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return true, "authorized", nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// authzServerImpl implements the AuthzService gRPC server.
|
|
||||||
type authzServerImpl struct {
|
|
||||||
authzv1.UnimplementedAuthzServiceServer
|
|
||||||
service *authzService
|
|
||||||
logger *zap.Logger
|
|
||||||
}
|
|
||||||
|
|
||||||
// Authorize checks if a user has a specific permission.
|
|
||||||
func (s *authzServerImpl) Authorize(ctx context.Context, req *authzv1.AuthorizeRequest) (*authzv1.AuthorizeResponse, error) {
|
|
||||||
authorized, message, err := s.service.authorize(ctx, req.UserId, req.Permission)
|
|
||||||
if err != nil {
|
|
||||||
return nil, status.Errorf(codes.Internal, "authorization check failed: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return &authzv1.AuthorizeResponse{
|
|
||||||
Authorized: authorized,
|
|
||||||
Message: message,
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// HasPermission checks if a user has a specific permission.
|
|
||||||
func (s *authzServerImpl) HasPermission(ctx context.Context, req *authzv1.HasPermissionRequest) (*authzv1.HasPermissionResponse, error) {
|
|
||||||
hasPerm, err := s.service.hasPermission(ctx, req.UserId, req.Permission)
|
|
||||||
if err != nil {
|
|
||||||
return nil, status.Errorf(codes.Internal, "permission check failed: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return &authzv1.HasPermissionResponse{
|
|
||||||
HasPermission: hasPerm,
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetUserPermissions returns all permissions for a user.
|
|
||||||
func (s *authzServerImpl) GetUserPermissions(ctx context.Context, req *authzv1.GetUserPermissionsRequest) (*authzv1.GetUserPermissionsResponse, error) {
|
|
||||||
permissions, err := s.service.getUserPermissions(ctx, req.UserId)
|
|
||||||
if err != nil {
|
|
||||||
return nil, status.Errorf(codes.Internal, "failed to get user permissions: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
protoPerms := make([]*authzv1.Permission, 0, len(permissions))
|
|
||||||
for _, perm := range permissions {
|
|
||||||
protoPerms = append(protoPerms, &authzv1.Permission{
|
|
||||||
Id: perm.ID,
|
|
||||||
Code: perm.Name, // Permission.Name is the code (e.g., "blog.post.create")
|
|
||||||
Name: perm.Name,
|
|
||||||
Description: "", // Permission schema doesn't have description field
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
return &authzv1.GetUserPermissionsResponse{
|
|
||||||
Permissions: protoPerms,
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetUserRoles returns all roles for a user.
|
|
||||||
func (s *authzServerImpl) GetUserRoles(ctx context.Context, req *authzv1.GetUserRolesRequest) (*authzv1.GetUserRolesResponse, error) {
|
|
||||||
roles, err := s.service.getUserRoles(ctx, req.UserId)
|
|
||||||
if err != nil {
|
|
||||||
return nil, status.Errorf(codes.Internal, "failed to get user roles: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
protoRoles := make([]*authzv1.Role, 0, len(roles))
|
|
||||||
for _, role := range roles {
|
|
||||||
// Get permission codes for this role
|
|
||||||
permCodes := make([]string, 0)
|
|
||||||
if role.Edges.RolePermissions != nil {
|
|
||||||
for _, rp := range role.Edges.RolePermissions {
|
|
||||||
if rp.Edges.Permission != nil {
|
|
||||||
permCodes = append(permCodes, rp.Edges.Permission.Name)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protoRoles = append(protoRoles, &authzv1.Role{
|
|
||||||
Id: role.ID,
|
|
||||||
Name: role.Name,
|
|
||||||
Description: role.Description,
|
|
||||||
Permissions: permCodes,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
return &authzv1.GetUserRolesResponse{
|
|
||||||
Roles: protoRoles,
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// provideAuthzService creates the authz service and gRPC server.
|
|
||||||
func provideAuthzService() fx.Option {
|
|
||||||
return fx.Options(
|
|
||||||
// Authz service
|
|
||||||
fx.Provide(func(client *database.Client, log logger.Logger) (*authzService, error) {
|
|
||||||
return &authzService{
|
|
||||||
client: client,
|
|
||||||
logger: log,
|
|
||||||
}, nil
|
|
||||||
}),
|
|
||||||
|
|
||||||
// gRPC server implementation
|
|
||||||
fx.Provide(func(authzService *authzService, log logger.Logger) (*authzServerImpl, error) {
|
|
||||||
zapLogger, _ := zap.NewProduction()
|
|
||||||
return &authzServerImpl{
|
|
||||||
service: authzService,
|
|
||||||
logger: zapLogger,
|
|
||||||
}, nil
|
|
||||||
}),
|
|
||||||
|
|
||||||
// gRPC server wrapper
|
|
||||||
fx.Provide(func(
|
|
||||||
serverImpl *authzServerImpl,
|
|
||||||
cfg config.ConfigProvider,
|
|
||||||
log logger.Logger,
|
|
||||||
) (*grpcServerWrapper, error) {
|
|
||||||
port := cfg.GetInt("services.authz.port")
|
|
||||||
if port == 0 {
|
|
||||||
port = 8083
|
|
||||||
}
|
|
||||||
|
|
||||||
addr := fmt.Sprintf("0.0.0.0:%d", port)
|
|
||||||
listener, err := net.Listen("tcp", addr)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to listen on %s: %w", addr, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
grpcServer := grpc.NewServer()
|
|
||||||
authzv1.RegisterAuthzServiceServer(grpcServer, serverImpl)
|
|
||||||
|
|
||||||
// Register health service
|
|
||||||
healthServer := health.NewServer()
|
|
||||||
grpc_health_v1.RegisterHealthServer(grpcServer, healthServer)
|
|
||||||
// Set serving status for the default service (empty string) - this is what Consul checks
|
|
||||||
healthServer.SetServingStatus("", grpc_health_v1.HealthCheckResponse_SERVING)
|
|
||||||
// Also set for the specific service name
|
|
||||||
healthServer.SetServingStatus("authz.v1.AuthzService", grpc_health_v1.HealthCheckResponse_SERVING)
|
|
||||||
|
|
||||||
// Register reflection for grpcurl
|
|
||||||
reflection.Register(grpcServer)
|
|
||||||
|
|
||||||
return &grpcServerWrapper{
|
|
||||||
server: grpcServer,
|
|
||||||
listener: listener,
|
|
||||||
port: port,
|
|
||||||
logger: log,
|
|
||||||
}, nil
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -1,228 +0,0 @@
|
|||||||
// Package main provides the entry point for the Authz Service.
|
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
"net"
|
|
||||||
"os"
|
|
||||||
"os/signal"
|
|
||||||
"syscall"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"git.dcentral.systems/toolz/goplt/internal/di"
|
|
||||||
healthpkg "git.dcentral.systems/toolz/goplt/internal/health"
|
|
||||||
"git.dcentral.systems/toolz/goplt/internal/infra/database"
|
|
||||||
"git.dcentral.systems/toolz/goplt/pkg/config"
|
|
||||||
"git.dcentral.systems/toolz/goplt/pkg/logger"
|
|
||||||
"git.dcentral.systems/toolz/goplt/pkg/registry"
|
|
||||||
"go.uber.org/fx"
|
|
||||||
"go.uber.org/zap"
|
|
||||||
"google.golang.org/grpc"
|
|
||||||
)
|
|
||||||
|
|
||||||
// grpcServerWrapper wraps the gRPC server for lifecycle management.
|
|
||||||
type grpcServerWrapper struct {
|
|
||||||
server *grpc.Server
|
|
||||||
listener net.Listener
|
|
||||||
port int
|
|
||||||
logger logger.Logger
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *grpcServerWrapper) Start() error {
|
|
||||||
s.logger.Info("Starting Authz Service gRPC server",
|
|
||||||
zap.Int("port", s.port),
|
|
||||||
zap.String("addr", s.listener.Addr().String()),
|
|
||||||
)
|
|
||||||
|
|
||||||
errChan := make(chan error, 1)
|
|
||||||
go func() {
|
|
||||||
if err := s.server.Serve(s.listener); err != nil {
|
|
||||||
errChan <- err
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
select {
|
|
||||||
case err := <-errChan:
|
|
||||||
return fmt.Errorf("gRPC server failed to start: %w", err)
|
|
||||||
case <-time.After(100 * time.Millisecond):
|
|
||||||
s.logger.Info("Authz Service gRPC server started successfully",
|
|
||||||
zap.Int("port", s.port),
|
|
||||||
)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *grpcServerWrapper) Stop(ctx context.Context) error {
|
|
||||||
s.logger.Info("Stopping Authz Service gRPC server")
|
|
||||||
|
|
||||||
stopped := make(chan struct{})
|
|
||||||
go func() {
|
|
||||||
s.server.GracefulStop()
|
|
||||||
close(stopped)
|
|
||||||
}()
|
|
||||||
|
|
||||||
select {
|
|
||||||
case <-stopped:
|
|
||||||
s.logger.Info("Authz Service gRPC server stopped gracefully")
|
|
||||||
return nil
|
|
||||||
case <-ctx.Done():
|
|
||||||
s.logger.Warn("Authz Service gRPC server stop timeout, forcing stop")
|
|
||||||
s.server.Stop()
|
|
||||||
return ctx.Err()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *grpcServerWrapper) Port() int {
|
|
||||||
return s.port
|
|
||||||
}
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
// Create DI container
|
|
||||||
// Note: CoreModule() is automatically included by NewContainer()
|
|
||||||
container := di.NewContainer(
|
|
||||||
// Database for authz service (authz schema)
|
|
||||||
fx.Provide(func(cfg config.ConfigProvider, log logger.Logger) (*database.Client, error) {
|
|
||||||
dsn := cfg.GetString("database.dsn")
|
|
||||||
if dsn == "" {
|
|
||||||
return nil, fmt.Errorf("database.dsn is required")
|
|
||||||
}
|
|
||||||
client, err := database.NewClientWithSchema(dsn, "authz")
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Run migrations
|
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
|
|
||||||
defer cancel()
|
|
||||||
|
|
||||||
if err := client.Migrate(ctx); err != nil {
|
|
||||||
log.Warn("Failed to run migrations",
|
|
||||||
zap.Error(err),
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
log.Info("Database migrations completed for authz service")
|
|
||||||
}
|
|
||||||
|
|
||||||
return client, nil
|
|
||||||
}),
|
|
||||||
|
|
||||||
// Register database health checker with existing health registry
|
|
||||||
fx.Invoke(func(registry *healthpkg.Registry, db *database.Client) {
|
|
||||||
registry.Register("database", healthpkg.NewDatabaseChecker(db))
|
|
||||||
}),
|
|
||||||
|
|
||||||
// Provide authz service and gRPC server (defined in authz_service_fx.go)
|
|
||||||
provideAuthzService(),
|
|
||||||
|
|
||||||
// Lifecycle hooks
|
|
||||||
fx.Invoke(registerLifecycle),
|
|
||||||
)
|
|
||||||
|
|
||||||
// Create root context
|
|
||||||
ctx, cancel := context.WithCancel(context.Background())
|
|
||||||
defer cancel()
|
|
||||||
|
|
||||||
// Handle signals
|
|
||||||
sigChan := make(chan os.Signal, 1)
|
|
||||||
signal.Notify(sigChan, os.Interrupt, syscall.SIGTERM)
|
|
||||||
|
|
||||||
// Start the application
|
|
||||||
if err := container.Start(ctx); err != nil {
|
|
||||||
log := logger.GetGlobalLogger()
|
|
||||||
if log != nil {
|
|
||||||
log.Error("Failed to start Authz Service",
|
|
||||||
logger.Error(err),
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
fmt.Fprintf(os.Stderr, "Failed to start Authz Service: %v\n", err)
|
|
||||||
}
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Wait for interrupt signal
|
|
||||||
<-sigChan
|
|
||||||
fmt.Println("\nShutting down Authz Service...")
|
|
||||||
|
|
||||||
// Create shutdown context with timeout
|
|
||||||
shutdownCtx, shutdownCancel := context.WithTimeout(context.Background(), 30*time.Second)
|
|
||||||
defer shutdownCancel()
|
|
||||||
|
|
||||||
// Stop the application
|
|
||||||
if err := container.Stop(shutdownCtx); err != nil {
|
|
||||||
log := logger.GetGlobalLogger()
|
|
||||||
if log != nil {
|
|
||||||
log.Error("Error during Authz Service shutdown",
|
|
||||||
logger.Error(err),
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
fmt.Fprintf(os.Stderr, "Error during shutdown: %v\n", err)
|
|
||||||
}
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
fmt.Println("Authz Service stopped successfully")
|
|
||||||
}
|
|
||||||
|
|
||||||
// registerLifecycle registers lifecycle hooks for the service.
|
|
||||||
func registerLifecycle(
|
|
||||||
lc fx.Lifecycle,
|
|
||||||
grpcServer *grpcServerWrapper,
|
|
||||||
serviceRegistry registry.ServiceRegistry,
|
|
||||||
cfg config.ConfigProvider,
|
|
||||||
log logger.Logger,
|
|
||||||
) {
|
|
||||||
lc.Append(fx.Hook{
|
|
||||||
OnStart: func(ctx context.Context) error {
|
|
||||||
// Start gRPC server
|
|
||||||
if err := grpcServer.Start(); err != nil {
|
|
||||||
return fmt.Errorf("failed to start gRPC server: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Register with service registry
|
|
||||||
serviceID := fmt.Sprintf("authz-service-%d", time.Now().Unix())
|
|
||||||
// In Docker, always use the Docker service name for health checks
|
|
||||||
// Consul (also in Docker) needs to reach the service via Docker DNS
|
|
||||||
host := cfg.GetString("services.authz.host")
|
|
||||||
if os.Getenv("ENVIRONMENT") == "production" || os.Getenv("DOCKER") == "true" {
|
|
||||||
host = "authz-service" // Docker service name - required for Consul health checks
|
|
||||||
} else if host == "" {
|
|
||||||
host = "localhost" // Local development
|
|
||||||
}
|
|
||||||
port := grpcServer.Port()
|
|
||||||
|
|
||||||
instance := ®istry.ServiceInstance{
|
|
||||||
ID: serviceID,
|
|
||||||
Name: "authz-service",
|
|
||||||
Address: host,
|
|
||||||
Port: port,
|
|
||||||
Tags: []string{"grpc", "authz"},
|
|
||||||
Metadata: map[string]string{
|
|
||||||
"version": "1.0.0",
|
|
||||||
"protocol": "grpc",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := serviceRegistry.Register(ctx, instance); err != nil {
|
|
||||||
log.Warn("Failed to register with service registry",
|
|
||||||
zap.Error(err),
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
log.Info("Registered Authz Service with service registry",
|
|
||||||
zap.String("service_id", serviceID),
|
|
||||||
zap.String("name", instance.Name),
|
|
||||||
zap.Int("port", port),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
},
|
|
||||||
OnStop: func(ctx context.Context) error {
|
|
||||||
// Stop gRPC server
|
|
||||||
if err := grpcServer.Stop(ctx); err != nil {
|
|
||||||
return fmt.Errorf("failed to stop gRPC server: %w", err)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
},
|
|
||||||
})
|
|
||||||
}
|
|
||||||
@@ -1,36 +0,0 @@
|
|||||||
# Build stage
|
|
||||||
FROM golang:1.25-alpine AS builder
|
|
||||||
|
|
||||||
WORKDIR /build
|
|
||||||
|
|
||||||
# Install build dependencies
|
|
||||||
RUN apk add --no-cache git make
|
|
||||||
|
|
||||||
# Copy go mod files
|
|
||||||
COPY go.mod go.sum ./
|
|
||||||
RUN go mod download
|
|
||||||
|
|
||||||
# Copy source code
|
|
||||||
COPY . .
|
|
||||||
|
|
||||||
# Build the service
|
|
||||||
WORKDIR /build/cmd/identity-service
|
|
||||||
RUN CGO_ENABLED=0 GOOS=linux go build -o identity-service -a -installsuffix cgo .
|
|
||||||
|
|
||||||
# Runtime stage
|
|
||||||
FROM alpine:latest
|
|
||||||
|
|
||||||
RUN apk --no-cache add ca-certificates tzdata
|
|
||||||
|
|
||||||
WORKDIR /app
|
|
||||||
|
|
||||||
# Copy binary from builder
|
|
||||||
COPY --from=builder /build/cmd/identity-service/identity-service .
|
|
||||||
|
|
||||||
# Copy config files
|
|
||||||
COPY --from=builder /build/config ./config
|
|
||||||
|
|
||||||
EXPOSE 8082
|
|
||||||
|
|
||||||
CMD ["./identity-service"]
|
|
||||||
|
|
||||||
@@ -1,448 +0,0 @@
|
|||||||
// Package main provides FX providers for Identity Service.
|
|
||||||
// This file creates the service inline to avoid importing internal packages.
|
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"crypto/rand"
|
|
||||||
"crypto/subtle"
|
|
||||||
"encoding/base64"
|
|
||||||
"fmt"
|
|
||||||
"math"
|
|
||||||
"net"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
identityv1 "git.dcentral.systems/toolz/goplt/api/proto/generated/identity/v1"
|
|
||||||
"git.dcentral.systems/toolz/goplt/internal/ent"
|
|
||||||
"git.dcentral.systems/toolz/goplt/internal/ent/user"
|
|
||||||
"git.dcentral.systems/toolz/goplt/internal/infra/database"
|
|
||||||
"git.dcentral.systems/toolz/goplt/pkg/config"
|
|
||||||
"git.dcentral.systems/toolz/goplt/pkg/logger"
|
|
||||||
"go.uber.org/fx"
|
|
||||||
"go.uber.org/zap"
|
|
||||||
"golang.org/x/crypto/argon2"
|
|
||||||
"google.golang.org/grpc"
|
|
||||||
"google.golang.org/grpc/codes"
|
|
||||||
"google.golang.org/grpc/health"
|
|
||||||
"google.golang.org/grpc/health/grpc_health_v1"
|
|
||||||
"google.golang.org/grpc/reflection"
|
|
||||||
"google.golang.org/grpc/status"
|
|
||||||
)
|
|
||||||
|
|
||||||
// userService provides user management functionality.
|
|
||||||
type userService struct {
|
|
||||||
client *database.Client
|
|
||||||
logger logger.Logger
|
|
||||||
}
|
|
||||||
|
|
||||||
// generateToken generates a random token.
|
|
||||||
func generateToken() (string, error) {
|
|
||||||
b := make([]byte, 32)
|
|
||||||
if _, err := rand.Read(b); err != nil {
|
|
||||||
return "", fmt.Errorf("failed to generate token: %w", err)
|
|
||||||
}
|
|
||||||
return fmt.Sprintf("%x", b), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// hashPassword hashes a password using argon2id.
|
|
||||||
func hashPassword(password string) (string, error) {
|
|
||||||
salt := make([]byte, 16)
|
|
||||||
if _, err := rand.Read(salt); err != nil {
|
|
||||||
return "", fmt.Errorf("failed to generate salt: %w", err)
|
|
||||||
}
|
|
||||||
hash := argon2.IDKey([]byte(password), salt, 3, 64*1024, 4, 32)
|
|
||||||
b64Salt := base64.RawStdEncoding.EncodeToString(salt)
|
|
||||||
b64Hash := base64.RawStdEncoding.EncodeToString(hash)
|
|
||||||
return fmt.Sprintf("$argon2id$v=%d$m=%d,t=%d,p=%d$%s$%s",
|
|
||||||
argon2.Version, 64*1024, 3, 4, b64Salt, b64Hash), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// verifyPassword verifies a password against a hash.
|
|
||||||
func verifyPassword(password, hash string) (bool, error) {
|
|
||||||
// Simplified verification - in production use proper parsing
|
|
||||||
parts := strings.Split(hash, "$")
|
|
||||||
if len(parts) != 6 {
|
|
||||||
return false, fmt.Errorf("invalid hash format")
|
|
||||||
}
|
|
||||||
salt, err := base64.RawStdEncoding.DecodeString(parts[4])
|
|
||||||
if err != nil {
|
|
||||||
return false, err
|
|
||||||
}
|
|
||||||
expectedHash, err := base64.RawStdEncoding.DecodeString(parts[5])
|
|
||||||
if err != nil {
|
|
||||||
return false, err
|
|
||||||
}
|
|
||||||
hashLen := len(expectedHash)
|
|
||||||
if hashLen < 0 || hashLen > math.MaxUint32 {
|
|
||||||
return false, fmt.Errorf("invalid hash length: %d", hashLen)
|
|
||||||
}
|
|
||||||
var hashLenUint32 uint32
|
|
||||||
if hashLen > math.MaxUint32 {
|
|
||||||
hashLenUint32 = math.MaxUint32
|
|
||||||
} else {
|
|
||||||
hashLenUint32 = uint32(hashLen)
|
|
||||||
}
|
|
||||||
actualHash := argon2.IDKey([]byte(password), salt, 3, 64*1024, 4, hashLenUint32)
|
|
||||||
return subtle.ConstantTimeCompare(expectedHash, actualHash) == 1, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// createUser creates a new user.
|
|
||||||
func (s *userService) createUser(ctx context.Context, email, username, pwd, firstName, lastName string) (*ent.User, error) {
|
|
||||||
exists, err := s.client.User.Query().Where(user.Email(email)).Exist(ctx)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to check email: %w", err)
|
|
||||||
}
|
|
||||||
if exists {
|
|
||||||
return nil, fmt.Errorf("email already exists")
|
|
||||||
}
|
|
||||||
|
|
||||||
passwordHash, err := hashPassword(pwd)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to hash password: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
verificationToken, err := generateToken()
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to generate token: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
create := s.client.User.Create().
|
|
||||||
SetID(fmt.Sprintf("%d", time.Now().UnixNano())).
|
|
||||||
SetEmail(email).
|
|
||||||
SetPasswordHash(passwordHash).
|
|
||||||
SetVerified(false).
|
|
||||||
SetEmailVerificationToken(verificationToken)
|
|
||||||
|
|
||||||
if username != "" {
|
|
||||||
create = create.SetUsername(username)
|
|
||||||
}
|
|
||||||
if firstName != "" {
|
|
||||||
create = create.SetFirstName(firstName)
|
|
||||||
}
|
|
||||||
if lastName != "" {
|
|
||||||
create = create.SetLastName(lastName)
|
|
||||||
}
|
|
||||||
|
|
||||||
u, err := create.Save(ctx)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to create user: %w", err)
|
|
||||||
}
|
|
||||||
return u, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// getUser retrieves a user by ID.
|
|
||||||
func (s *userService) getUser(ctx context.Context, id string) (*ent.User, error) {
|
|
||||||
u, err := s.client.User.Get(ctx, id)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to get user: %w", err)
|
|
||||||
}
|
|
||||||
return u, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// getUserByEmail retrieves a user by email.
|
|
||||||
func (s *userService) getUserByEmail(ctx context.Context, email string) (*ent.User, error) {
|
|
||||||
u, err := s.client.User.Query().Where(user.Email(email)).Only(ctx)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to get user: %w", err)
|
|
||||||
}
|
|
||||||
return u, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// updateUser updates a user.
|
|
||||||
func (s *userService) updateUser(ctx context.Context, id string, email, username, firstName, lastName *string) (*ent.User, error) {
|
|
||||||
update := s.client.User.UpdateOneID(id)
|
|
||||||
if email != nil {
|
|
||||||
exists, err := s.client.User.Query().
|
|
||||||
Where(user.Email(*email), user.IDNEQ(id)).
|
|
||||||
Exist(ctx)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to check email: %w", err)
|
|
||||||
}
|
|
||||||
if exists {
|
|
||||||
return nil, fmt.Errorf("email already taken")
|
|
||||||
}
|
|
||||||
update = update.SetEmail(*email)
|
|
||||||
}
|
|
||||||
if username != nil {
|
|
||||||
update = update.SetUsername(*username)
|
|
||||||
}
|
|
||||||
if firstName != nil {
|
|
||||||
update = update.SetFirstName(*firstName)
|
|
||||||
}
|
|
||||||
if lastName != nil {
|
|
||||||
update = update.SetLastName(*lastName)
|
|
||||||
}
|
|
||||||
return update.Save(ctx)
|
|
||||||
}
|
|
||||||
|
|
||||||
// deleteUser deletes a user.
|
|
||||||
func (s *userService) deleteUser(ctx context.Context, id string) error {
|
|
||||||
return s.client.User.DeleteOneID(id).Exec(ctx)
|
|
||||||
}
|
|
||||||
|
|
||||||
// verifyEmail verifies a user's email.
|
|
||||||
func (s *userService) verifyEmail(ctx context.Context, token string) error {
|
|
||||||
u, err := s.client.User.Query().Where(user.EmailVerificationToken(token)).Only(ctx)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("invalid token")
|
|
||||||
}
|
|
||||||
_, err = s.client.User.UpdateOneID(u.ID).
|
|
||||||
SetVerified(true).
|
|
||||||
ClearEmailVerificationToken().
|
|
||||||
Save(ctx)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// requestPasswordReset requests a password reset.
|
|
||||||
func (s *userService) requestPasswordReset(ctx context.Context, email string) (string, error) {
|
|
||||||
u, err := s.getUserByEmail(ctx, email)
|
|
||||||
if err != nil {
|
|
||||||
return "", nil // Don't reveal if user exists
|
|
||||||
}
|
|
||||||
token, err := generateToken()
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
expiresAt := time.Now().Add(24 * time.Hour)
|
|
||||||
_, err = s.client.User.UpdateOneID(u.ID).
|
|
||||||
SetPasswordResetToken(token).
|
|
||||||
SetPasswordResetExpiresAt(expiresAt).
|
|
||||||
Save(ctx)
|
|
||||||
return token, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// resetPassword resets a password.
|
|
||||||
func (s *userService) resetPassword(ctx context.Context, token, newPassword string) error {
|
|
||||||
u, err := s.client.User.Query().Where(user.PasswordResetToken(token)).Only(ctx)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("invalid token")
|
|
||||||
}
|
|
||||||
if !u.PasswordResetExpiresAt.IsZero() && u.PasswordResetExpiresAt.Before(time.Now()) {
|
|
||||||
return fmt.Errorf("token expired")
|
|
||||||
}
|
|
||||||
passwordHash, err := hashPassword(newPassword)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
_, err = s.client.User.UpdateOneID(u.ID).
|
|
||||||
SetPasswordHash(passwordHash).
|
|
||||||
ClearPasswordResetToken().
|
|
||||||
ClearPasswordResetExpiresAt().
|
|
||||||
Save(ctx)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// verifyPassword verifies a password.
|
|
||||||
func (s *userService) verifyPassword(ctx context.Context, email, pwd string) (*ent.User, error) {
|
|
||||||
u, err := s.getUserByEmail(ctx, email)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
valid, err := verifyPassword(pwd, u.PasswordHash)
|
|
||||||
if err != nil || !valid {
|
|
||||||
return nil, fmt.Errorf("invalid password")
|
|
||||||
}
|
|
||||||
return u, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// identityServerImpl implements the IdentityService gRPC server.
|
|
||||||
type identityServerImpl struct {
|
|
||||||
identityv1.UnimplementedIdentityServiceServer
|
|
||||||
service *userService
|
|
||||||
logger *zap.Logger
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetUser retrieves a user by ID.
|
|
||||||
func (s *identityServerImpl) GetUser(ctx context.Context, req *identityv1.GetUserRequest) (*identityv1.GetUserResponse, error) {
|
|
||||||
u, err := s.service.getUser(ctx, req.Id)
|
|
||||||
if err != nil {
|
|
||||||
return nil, status.Errorf(codes.NotFound, "user not found: %v", err)
|
|
||||||
}
|
|
||||||
return &identityv1.GetUserResponse{
|
|
||||||
User: &identityv1.User{
|
|
||||||
Id: u.ID,
|
|
||||||
Email: u.Email,
|
|
||||||
Username: u.Username,
|
|
||||||
FirstName: u.FirstName,
|
|
||||||
LastName: u.LastName,
|
|
||||||
EmailVerified: u.Verified,
|
|
||||||
CreatedAt: u.CreatedAt.Unix(),
|
|
||||||
UpdatedAt: u.UpdatedAt.Unix(),
|
|
||||||
},
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetUserByEmail retrieves a user by email.
|
|
||||||
func (s *identityServerImpl) GetUserByEmail(ctx context.Context, req *identityv1.GetUserByEmailRequest) (*identityv1.GetUserByEmailResponse, error) {
|
|
||||||
u, err := s.service.getUserByEmail(ctx, req.Email)
|
|
||||||
if err != nil {
|
|
||||||
return nil, status.Errorf(codes.NotFound, "user not found: %v", err)
|
|
||||||
}
|
|
||||||
return &identityv1.GetUserByEmailResponse{
|
|
||||||
User: &identityv1.User{
|
|
||||||
Id: u.ID,
|
|
||||||
Email: u.Email,
|
|
||||||
Username: u.Username,
|
|
||||||
FirstName: u.FirstName,
|
|
||||||
LastName: u.LastName,
|
|
||||||
EmailVerified: u.Verified,
|
|
||||||
CreatedAt: u.CreatedAt.Unix(),
|
|
||||||
UpdatedAt: u.UpdatedAt.Unix(),
|
|
||||||
},
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// CreateUser creates a new user.
|
|
||||||
func (s *identityServerImpl) CreateUser(ctx context.Context, req *identityv1.CreateUserRequest) (*identityv1.CreateUserResponse, error) {
|
|
||||||
u, err := s.service.createUser(ctx, req.Email, req.Username, req.Password, req.FirstName, req.LastName)
|
|
||||||
if err != nil {
|
|
||||||
return nil, status.Errorf(codes.Internal, "failed to create user: %v", err)
|
|
||||||
}
|
|
||||||
return &identityv1.CreateUserResponse{
|
|
||||||
User: &identityv1.User{
|
|
||||||
Id: u.ID,
|
|
||||||
Email: u.Email,
|
|
||||||
Username: u.Username,
|
|
||||||
FirstName: u.FirstName,
|
|
||||||
LastName: u.LastName,
|
|
||||||
EmailVerified: u.Verified,
|
|
||||||
CreatedAt: u.CreatedAt.Unix(),
|
|
||||||
UpdatedAt: u.UpdatedAt.Unix(),
|
|
||||||
},
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// UpdateUser updates a user.
|
|
||||||
func (s *identityServerImpl) UpdateUser(ctx context.Context, req *identityv1.UpdateUserRequest) (*identityv1.UpdateUserResponse, error) {
|
|
||||||
u, err := s.service.updateUser(ctx, req.Id, req.Email, req.Username, req.FirstName, req.LastName)
|
|
||||||
if err != nil {
|
|
||||||
return nil, status.Errorf(codes.Internal, "failed to update user: %v", err)
|
|
||||||
}
|
|
||||||
return &identityv1.UpdateUserResponse{
|
|
||||||
User: &identityv1.User{
|
|
||||||
Id: u.ID,
|
|
||||||
Email: u.Email,
|
|
||||||
Username: u.Username,
|
|
||||||
FirstName: u.FirstName,
|
|
||||||
LastName: u.LastName,
|
|
||||||
EmailVerified: u.Verified,
|
|
||||||
CreatedAt: u.CreatedAt.Unix(),
|
|
||||||
UpdatedAt: u.UpdatedAt.Unix(),
|
|
||||||
},
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// DeleteUser deletes a user.
|
|
||||||
func (s *identityServerImpl) DeleteUser(ctx context.Context, req *identityv1.DeleteUserRequest) (*identityv1.DeleteUserResponse, error) {
|
|
||||||
if err := s.service.deleteUser(ctx, req.Id); err != nil {
|
|
||||||
return nil, status.Errorf(codes.Internal, "failed to delete user: %v", err)
|
|
||||||
}
|
|
||||||
return &identityv1.DeleteUserResponse{Success: true}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// VerifyEmail verifies a user's email.
|
|
||||||
func (s *identityServerImpl) VerifyEmail(ctx context.Context, req *identityv1.VerifyEmailRequest) (*identityv1.VerifyEmailResponse, error) {
|
|
||||||
if err := s.service.verifyEmail(ctx, req.Token); err != nil {
|
|
||||||
return nil, status.Errorf(codes.InvalidArgument, "failed to verify email: %v", err)
|
|
||||||
}
|
|
||||||
return &identityv1.VerifyEmailResponse{Success: true}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// RequestPasswordReset requests a password reset.
|
|
||||||
func (s *identityServerImpl) RequestPasswordReset(ctx context.Context, req *identityv1.RequestPasswordResetRequest) (*identityv1.RequestPasswordResetResponse, error) {
|
|
||||||
_, err := s.service.requestPasswordReset(ctx, req.Email)
|
|
||||||
if err != nil {
|
|
||||||
return nil, status.Errorf(codes.Internal, "failed to request password reset: %v", err)
|
|
||||||
}
|
|
||||||
return &identityv1.RequestPasswordResetResponse{Success: true}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// ResetPassword resets a password.
|
|
||||||
func (s *identityServerImpl) ResetPassword(ctx context.Context, req *identityv1.ResetPasswordRequest) (*identityv1.ResetPasswordResponse, error) {
|
|
||||||
if err := s.service.resetPassword(ctx, req.Token, req.NewPassword); err != nil {
|
|
||||||
return nil, status.Errorf(codes.InvalidArgument, "failed to reset password: %v", err)
|
|
||||||
}
|
|
||||||
return &identityv1.ResetPasswordResponse{Success: true}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// VerifyPassword verifies a user's password.
|
|
||||||
func (s *identityServerImpl) VerifyPassword(ctx context.Context, req *identityv1.VerifyPasswordRequest) (*identityv1.VerifyPasswordResponse, error) {
|
|
||||||
u, err := s.service.verifyPassword(ctx, req.Email, req.Password)
|
|
||||||
if err != nil {
|
|
||||||
return nil, status.Errorf(codes.Unauthenticated, "invalid credentials: %v", err)
|
|
||||||
}
|
|
||||||
return &identityv1.VerifyPasswordResponse{
|
|
||||||
User: &identityv1.User{
|
|
||||||
Id: u.ID,
|
|
||||||
Email: u.Email,
|
|
||||||
Username: u.Username,
|
|
||||||
FirstName: u.FirstName,
|
|
||||||
LastName: u.LastName,
|
|
||||||
EmailVerified: u.Verified,
|
|
||||||
CreatedAt: u.CreatedAt.Unix(),
|
|
||||||
UpdatedAt: u.UpdatedAt.Unix(),
|
|
||||||
},
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// provideIdentityService creates the identity service and gRPC server.
|
|
||||||
func provideIdentityService() fx.Option {
|
|
||||||
return fx.Options(
|
|
||||||
// User service
|
|
||||||
fx.Provide(func(client *database.Client, log logger.Logger) (*userService, error) {
|
|
||||||
return &userService{
|
|
||||||
client: client,
|
|
||||||
logger: log,
|
|
||||||
}, nil
|
|
||||||
}),
|
|
||||||
|
|
||||||
// gRPC server implementation
|
|
||||||
fx.Provide(func(userService *userService, log logger.Logger) (*identityServerImpl, error) {
|
|
||||||
zapLogger, _ := zap.NewProduction()
|
|
||||||
return &identityServerImpl{
|
|
||||||
service: userService,
|
|
||||||
logger: zapLogger,
|
|
||||||
}, nil
|
|
||||||
}),
|
|
||||||
|
|
||||||
// gRPC server wrapper
|
|
||||||
fx.Provide(func(
|
|
||||||
serverImpl *identityServerImpl,
|
|
||||||
cfg config.ConfigProvider,
|
|
||||||
log logger.Logger,
|
|
||||||
) (*grpcServerWrapper, error) {
|
|
||||||
port := cfg.GetInt("services.identity.port")
|
|
||||||
if port == 0 {
|
|
||||||
port = 8082
|
|
||||||
}
|
|
||||||
|
|
||||||
addr := fmt.Sprintf("0.0.0.0:%d", port)
|
|
||||||
listener, err := net.Listen("tcp", addr)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to listen on %s: %w", addr, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
grpcServer := grpc.NewServer()
|
|
||||||
identityv1.RegisterIdentityServiceServer(grpcServer, serverImpl)
|
|
||||||
|
|
||||||
// Register health service
|
|
||||||
healthServer := health.NewServer()
|
|
||||||
grpc_health_v1.RegisterHealthServer(grpcServer, healthServer)
|
|
||||||
// Set serving status for the default service (empty string) - this is what Consul checks
|
|
||||||
healthServer.SetServingStatus("", grpc_health_v1.HealthCheckResponse_SERVING)
|
|
||||||
// Also set for the specific service name
|
|
||||||
healthServer.SetServingStatus("identity.v1.IdentityService", grpc_health_v1.HealthCheckResponse_SERVING)
|
|
||||||
|
|
||||||
// Register reflection for grpcurl
|
|
||||||
reflection.Register(grpcServer)
|
|
||||||
|
|
||||||
return &grpcServerWrapper{
|
|
||||||
server: grpcServer,
|
|
||||||
listener: listener,
|
|
||||||
port: port,
|
|
||||||
logger: log,
|
|
||||||
}, nil
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -1,228 +0,0 @@
|
|||||||
// Package main provides the entry point for the Identity Service.
|
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
"net"
|
|
||||||
"os"
|
|
||||||
"os/signal"
|
|
||||||
"syscall"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"git.dcentral.systems/toolz/goplt/internal/di"
|
|
||||||
healthpkg "git.dcentral.systems/toolz/goplt/internal/health"
|
|
||||||
"git.dcentral.systems/toolz/goplt/internal/infra/database"
|
|
||||||
"git.dcentral.systems/toolz/goplt/pkg/config"
|
|
||||||
"git.dcentral.systems/toolz/goplt/pkg/logger"
|
|
||||||
"git.dcentral.systems/toolz/goplt/pkg/registry"
|
|
||||||
"go.uber.org/fx"
|
|
||||||
"go.uber.org/zap"
|
|
||||||
"google.golang.org/grpc"
|
|
||||||
)
|
|
||||||
|
|
||||||
// grpcServerWrapper wraps the gRPC server for lifecycle management.
|
|
||||||
type grpcServerWrapper struct {
|
|
||||||
server *grpc.Server
|
|
||||||
listener net.Listener
|
|
||||||
port int
|
|
||||||
logger logger.Logger
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *grpcServerWrapper) Start() error {
|
|
||||||
s.logger.Info("Starting Identity Service gRPC server",
|
|
||||||
zap.Int("port", s.port),
|
|
||||||
zap.String("addr", s.listener.Addr().String()),
|
|
||||||
)
|
|
||||||
|
|
||||||
errChan := make(chan error, 1)
|
|
||||||
go func() {
|
|
||||||
if err := s.server.Serve(s.listener); err != nil {
|
|
||||||
errChan <- err
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
select {
|
|
||||||
case err := <-errChan:
|
|
||||||
return fmt.Errorf("gRPC server failed to start: %w", err)
|
|
||||||
case <-time.After(100 * time.Millisecond):
|
|
||||||
s.logger.Info("Identity Service gRPC server started successfully",
|
|
||||||
zap.Int("port", s.port),
|
|
||||||
)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *grpcServerWrapper) Stop(ctx context.Context) error {
|
|
||||||
s.logger.Info("Stopping Identity Service gRPC server")
|
|
||||||
|
|
||||||
stopped := make(chan struct{})
|
|
||||||
go func() {
|
|
||||||
s.server.GracefulStop()
|
|
||||||
close(stopped)
|
|
||||||
}()
|
|
||||||
|
|
||||||
select {
|
|
||||||
case <-stopped:
|
|
||||||
s.logger.Info("Identity Service gRPC server stopped gracefully")
|
|
||||||
return nil
|
|
||||||
case <-ctx.Done():
|
|
||||||
s.logger.Warn("Identity Service gRPC server stop timeout, forcing stop")
|
|
||||||
s.server.Stop()
|
|
||||||
return ctx.Err()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *grpcServerWrapper) Port() int {
|
|
||||||
return s.port
|
|
||||||
}
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
// Create DI container
|
|
||||||
// Note: CoreModule() is automatically included by NewContainer()
|
|
||||||
container := di.NewContainer(
|
|
||||||
// Database for identity service (identity schema)
|
|
||||||
fx.Provide(func(cfg config.ConfigProvider, log logger.Logger) (*database.Client, error) {
|
|
||||||
dsn := cfg.GetString("database.dsn")
|
|
||||||
if dsn == "" {
|
|
||||||
return nil, fmt.Errorf("database.dsn is required")
|
|
||||||
}
|
|
||||||
client, err := database.NewClientWithSchema(dsn, "identity")
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Run migrations
|
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
|
|
||||||
defer cancel()
|
|
||||||
|
|
||||||
if err := client.Migrate(ctx); err != nil {
|
|
||||||
log.Warn("Failed to run migrations",
|
|
||||||
zap.Error(err),
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
log.Info("Database migrations completed for identity service")
|
|
||||||
}
|
|
||||||
|
|
||||||
return client, nil
|
|
||||||
}),
|
|
||||||
|
|
||||||
// Register database health checker with existing health registry
|
|
||||||
fx.Invoke(func(registry *healthpkg.Registry, db *database.Client) {
|
|
||||||
registry.Register("database", healthpkg.NewDatabaseChecker(db))
|
|
||||||
}),
|
|
||||||
|
|
||||||
// Provide identity service and gRPC server (defined in identity_service_fx.go)
|
|
||||||
provideIdentityService(),
|
|
||||||
|
|
||||||
// Lifecycle hooks
|
|
||||||
fx.Invoke(registerLifecycle),
|
|
||||||
)
|
|
||||||
|
|
||||||
// Create root context
|
|
||||||
ctx, cancel := context.WithCancel(context.Background())
|
|
||||||
defer cancel()
|
|
||||||
|
|
||||||
// Handle signals
|
|
||||||
sigChan := make(chan os.Signal, 1)
|
|
||||||
signal.Notify(sigChan, os.Interrupt, syscall.SIGTERM)
|
|
||||||
|
|
||||||
// Start the application
|
|
||||||
if err := container.Start(ctx); err != nil {
|
|
||||||
log := logger.GetGlobalLogger()
|
|
||||||
if log != nil {
|
|
||||||
log.Error("Failed to start Identity Service",
|
|
||||||
logger.Error(err),
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
fmt.Fprintf(os.Stderr, "Failed to start Identity Service: %v\n", err)
|
|
||||||
}
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Wait for interrupt signal
|
|
||||||
<-sigChan
|
|
||||||
fmt.Println("\nShutting down Identity Service...")
|
|
||||||
|
|
||||||
// Create shutdown context with timeout
|
|
||||||
shutdownCtx, shutdownCancel := context.WithTimeout(context.Background(), 30*time.Second)
|
|
||||||
defer shutdownCancel()
|
|
||||||
|
|
||||||
// Stop the application
|
|
||||||
if err := container.Stop(shutdownCtx); err != nil {
|
|
||||||
log := logger.GetGlobalLogger()
|
|
||||||
if log != nil {
|
|
||||||
log.Error("Error during Identity Service shutdown",
|
|
||||||
logger.Error(err),
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
fmt.Fprintf(os.Stderr, "Error during shutdown: %v\n", err)
|
|
||||||
}
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
fmt.Println("Identity Service stopped successfully")
|
|
||||||
}
|
|
||||||
|
|
||||||
// registerLifecycle registers lifecycle hooks for the service.
|
|
||||||
func registerLifecycle(
|
|
||||||
lc fx.Lifecycle,
|
|
||||||
grpcServer *grpcServerWrapper,
|
|
||||||
serviceRegistry registry.ServiceRegistry,
|
|
||||||
cfg config.ConfigProvider,
|
|
||||||
log logger.Logger,
|
|
||||||
) {
|
|
||||||
lc.Append(fx.Hook{
|
|
||||||
OnStart: func(ctx context.Context) error {
|
|
||||||
// Start gRPC server
|
|
||||||
if err := grpcServer.Start(); err != nil {
|
|
||||||
return fmt.Errorf("failed to start gRPC server: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Register with service registry
|
|
||||||
serviceID := fmt.Sprintf("identity-service-%d", time.Now().Unix())
|
|
||||||
// In Docker, always use the Docker service name for health checks
|
|
||||||
// Consul (also in Docker) needs to reach the service via Docker DNS
|
|
||||||
host := cfg.GetString("services.identity.host")
|
|
||||||
if os.Getenv("ENVIRONMENT") == "production" || os.Getenv("DOCKER") == "true" {
|
|
||||||
host = "identity-service" // Docker service name - required for Consul health checks
|
|
||||||
} else if host == "" {
|
|
||||||
host = "localhost" // Local development
|
|
||||||
}
|
|
||||||
port := grpcServer.Port()
|
|
||||||
|
|
||||||
instance := ®istry.ServiceInstance{
|
|
||||||
ID: serviceID,
|
|
||||||
Name: "identity-service",
|
|
||||||
Address: host,
|
|
||||||
Port: port,
|
|
||||||
Tags: []string{"grpc", "identity"},
|
|
||||||
Metadata: map[string]string{
|
|
||||||
"version": "1.0.0",
|
|
||||||
"protocol": "grpc",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := serviceRegistry.Register(ctx, instance); err != nil {
|
|
||||||
log.Warn("Failed to register with service registry",
|
|
||||||
zap.Error(err),
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
log.Info("Registered Identity Service with service registry",
|
|
||||||
zap.String("service_id", serviceID),
|
|
||||||
zap.String("name", instance.Name),
|
|
||||||
zap.Int("port", port),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
},
|
|
||||||
OnStop: func(ctx context.Context) error {
|
|
||||||
// Stop gRPC server
|
|
||||||
if err := grpcServer.Stop(ctx); err != nil {
|
|
||||||
return fmt.Errorf("failed to stop gRPC server: %w", err)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
},
|
|
||||||
})
|
|
||||||
}
|
|
||||||
@@ -7,28 +7,23 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
|
|
||||||
"git.dcentral.systems/toolz/goplt/internal/di"
|
"git.dcentral.systems/toolz/goplt/internal/di"
|
||||||
"git.dcentral.systems/toolz/goplt/internal/health"
|
"git.dcentral.systems/toolz/goplt/internal/infra/database"
|
||||||
"git.dcentral.systems/toolz/goplt/internal/metrics"
|
"git.dcentral.systems/toolz/goplt/internal/server"
|
||||||
"git.dcentral.systems/toolz/goplt/pkg/config"
|
|
||||||
"git.dcentral.systems/toolz/goplt/pkg/logger"
|
"git.dcentral.systems/toolz/goplt/pkg/logger"
|
||||||
"go.uber.org/fx"
|
"go.uber.org/fx"
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
// Create DI container with lifecycle hooks
|
// Create DI container with lifecycle hooks
|
||||||
// This is a minimal entry point for testing core kernel infrastructure
|
// We need to invoke the HTTP server to ensure all providers execute
|
||||||
// Services will have their own entry points (cmd/{service}/main.go)
|
|
||||||
container := di.NewContainer(
|
container := di.NewContainer(
|
||||||
// Invoke lifecycle hooks
|
// Invoke lifecycle hooks
|
||||||
fx.Invoke(di.RegisterLifecycleHooks),
|
fx.Invoke(di.RegisterLifecycleHooks),
|
||||||
// Verify core kernel services are available
|
// Force HTTP server to be created (which triggers all dependencies)
|
||||||
fx.Invoke(func(
|
// This ensures database, health, metrics, etc. are all created
|
||||||
_ config.ConfigProvider,
|
fx.Invoke(func(srv *server.Server, dbClient *database.Client) {
|
||||||
_ logger.Logger,
|
// Both server and database are created, hooks are registered
|
||||||
_ *health.Registry,
|
// This ensures all providers execute
|
||||||
_ *metrics.Metrics,
|
|
||||||
) {
|
|
||||||
// Core kernel services are available
|
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
environment: development
|
environment: development
|
||||||
|
|
||||||
server:
|
server:
|
||||||
port: 8080
|
port: 3000
|
||||||
host: "0.0.0.0"
|
host: "0.0.0.0"
|
||||||
read_timeout: 30s
|
read_timeout: 30s
|
||||||
write_timeout: 30s
|
write_timeout: 30s
|
||||||
@@ -24,49 +24,3 @@ tracing:
|
|||||||
service_name: "platform"
|
service_name: "platform"
|
||||||
service_version: "1.0.0"
|
service_version: "1.0.0"
|
||||||
otlp_endpoint: ""
|
otlp_endpoint: ""
|
||||||
|
|
||||||
registry:
|
|
||||||
type: consul
|
|
||||||
consul:
|
|
||||||
address: "localhost:8500"
|
|
||||||
datacenter: "dc1"
|
|
||||||
scheme: "http"
|
|
||||||
health_check:
|
|
||||||
interval: "10s"
|
|
||||||
timeout: "3s"
|
|
||||||
deregister_after: "30s"
|
|
||||||
http: "/healthz"
|
|
||||||
grpc: "grpc.health.v1.Health"
|
|
||||||
use_grpc: true
|
|
||||||
|
|
||||||
services:
|
|
||||||
audit:
|
|
||||||
port: 8084
|
|
||||||
host: "localhost"
|
|
||||||
auth:
|
|
||||||
port: 8081
|
|
||||||
host: "localhost"
|
|
||||||
identity:
|
|
||||||
port: 8082
|
|
||||||
host: "localhost"
|
|
||||||
authz:
|
|
||||||
port: 8083
|
|
||||||
host: "localhost"
|
|
||||||
|
|
||||||
auth:
|
|
||||||
jwt_secret: "change-this-secret-in-production"
|
|
||||||
|
|
||||||
gateway:
|
|
||||||
port: 8080
|
|
||||||
host: "0.0.0.0"
|
|
||||||
routes:
|
|
||||||
- path: "/api/v1/auth/**"
|
|
||||||
service: "auth-service"
|
|
||||||
auth_required: false
|
|
||||||
- path: "/api/v1/users/**"
|
|
||||||
service: "identity-service"
|
|
||||||
auth_required: true
|
|
||||||
cors:
|
|
||||||
allowed_origins: ["*"]
|
|
||||||
allowed_methods: ["GET", "POST", "PUT", "DELETE", "PATCH"]
|
|
||||||
allowed_headers: ["Authorization", "Content-Type"]
|
|
||||||
|
|||||||
@@ -1,49 +0,0 @@
|
|||||||
# Development docker-compose: Only infrastructure services (PostgreSQL and Consul)
|
|
||||||
# Use this for local development when running services directly with `go run`
|
|
||||||
|
|
||||||
services:
|
|
||||||
postgres:
|
|
||||||
image: postgres:16-alpine
|
|
||||||
container_name: goplt-postgres
|
|
||||||
environment:
|
|
||||||
POSTGRES_USER: goplt
|
|
||||||
POSTGRES_PASSWORD: goplt_password
|
|
||||||
POSTGRES_DB: goplt
|
|
||||||
ports:
|
|
||||||
- "5432:5432"
|
|
||||||
volumes:
|
|
||||||
- postgres_data:/var/lib/postgresql/data
|
|
||||||
healthcheck:
|
|
||||||
test: ["CMD-SHELL", "pg_isready -U goplt"]
|
|
||||||
interval: 5s
|
|
||||||
timeout: 5s
|
|
||||||
retries: 5
|
|
||||||
networks:
|
|
||||||
- goplt-network
|
|
||||||
|
|
||||||
consul:
|
|
||||||
image: consul:latest
|
|
||||||
container_name: goplt-consul
|
|
||||||
command: consul agent -dev -client=0.0.0.0
|
|
||||||
ports:
|
|
||||||
- "8500:8500"
|
|
||||||
volumes:
|
|
||||||
- consul_data:/consul/data
|
|
||||||
healthcheck:
|
|
||||||
test: ["CMD-SHELL", "consul members"]
|
|
||||||
interval: 10s
|
|
||||||
timeout: 3s
|
|
||||||
retries: 5
|
|
||||||
networks:
|
|
||||||
- goplt-network
|
|
||||||
|
|
||||||
volumes:
|
|
||||||
postgres_data:
|
|
||||||
driver: local
|
|
||||||
consul_data:
|
|
||||||
driver: local
|
|
||||||
|
|
||||||
networks:
|
|
||||||
goplt-network:
|
|
||||||
driver: bridge
|
|
||||||
|
|
||||||
@@ -1,5 +1,4 @@
|
|||||||
# Full docker-compose: All services + infrastructure
|
version: '3.8'
|
||||||
# Use this to run the complete platform with all services in Docker
|
|
||||||
|
|
||||||
services:
|
services:
|
||||||
postgres:
|
postgres:
|
||||||
@@ -21,140 +20,11 @@ services:
|
|||||||
networks:
|
networks:
|
||||||
- goplt-network
|
- goplt-network
|
||||||
|
|
||||||
consul:
|
|
||||||
image: consul:1.15.4
|
|
||||||
container_name: goplt-consul
|
|
||||||
command: consul agent -dev -client=0.0.0.0
|
|
||||||
ports:
|
|
||||||
- "8500:8500"
|
|
||||||
volumes:
|
|
||||||
- consul_data:/consul/data
|
|
||||||
healthcheck:
|
|
||||||
test: ["CMD-SHELL", "consul members"]
|
|
||||||
interval: 10s
|
|
||||||
timeout: 3s
|
|
||||||
retries: 5
|
|
||||||
networks:
|
|
||||||
- goplt-network
|
|
||||||
|
|
||||||
auth-service:
|
|
||||||
build:
|
|
||||||
context: .
|
|
||||||
dockerfile: cmd/auth-service/Dockerfile
|
|
||||||
container_name: goplt-auth-service
|
|
||||||
environment:
|
|
||||||
ENVIRONMENT: production
|
|
||||||
DATABASE_DSN: "postgres://goplt:goplt_password@postgres:5432/goplt?sslmode=disable"
|
|
||||||
REGISTRY_TYPE: consul
|
|
||||||
REGISTRY_CONSUL_ADDRESS: "consul:8500"
|
|
||||||
ports:
|
|
||||||
- "8081:8081"
|
|
||||||
depends_on:
|
|
||||||
postgres:
|
|
||||||
condition: service_healthy
|
|
||||||
consul:
|
|
||||||
condition: service_healthy
|
|
||||||
networks:
|
|
||||||
- goplt-network
|
|
||||||
restart: unless-stopped
|
|
||||||
|
|
||||||
identity-service:
|
|
||||||
build:
|
|
||||||
context: .
|
|
||||||
dockerfile: cmd/identity-service/Dockerfile
|
|
||||||
container_name: goplt-identity-service
|
|
||||||
environment:
|
|
||||||
ENVIRONMENT: production
|
|
||||||
DATABASE_DSN: "postgres://goplt:goplt_password@postgres:5432/goplt?sslmode=disable"
|
|
||||||
REGISTRY_TYPE: consul
|
|
||||||
REGISTRY_CONSUL_ADDRESS: "consul:8500"
|
|
||||||
ports:
|
|
||||||
- "8082:8082"
|
|
||||||
depends_on:
|
|
||||||
postgres:
|
|
||||||
condition: service_healthy
|
|
||||||
consul:
|
|
||||||
condition: service_healthy
|
|
||||||
networks:
|
|
||||||
- goplt-network
|
|
||||||
restart: unless-stopped
|
|
||||||
|
|
||||||
authz-service:
|
|
||||||
build:
|
|
||||||
context: .
|
|
||||||
dockerfile: cmd/authz-service/Dockerfile
|
|
||||||
container_name: goplt-authz-service
|
|
||||||
environment:
|
|
||||||
ENVIRONMENT: production
|
|
||||||
DATABASE_DSN: "postgres://goplt:goplt_password@postgres:5432/goplt?sslmode=disable"
|
|
||||||
REGISTRY_TYPE: consul
|
|
||||||
REGISTRY_CONSUL_ADDRESS: "consul:8500"
|
|
||||||
ports:
|
|
||||||
- "8083:8083"
|
|
||||||
depends_on:
|
|
||||||
postgres:
|
|
||||||
condition: service_healthy
|
|
||||||
consul:
|
|
||||||
condition: service_healthy
|
|
||||||
networks:
|
|
||||||
- goplt-network
|
|
||||||
restart: unless-stopped
|
|
||||||
|
|
||||||
audit-service:
|
|
||||||
build:
|
|
||||||
context: .
|
|
||||||
dockerfile: cmd/audit-service/Dockerfile
|
|
||||||
container_name: goplt-audit-service
|
|
||||||
environment:
|
|
||||||
ENVIRONMENT: production
|
|
||||||
DATABASE_DSN: "postgres://goplt:goplt_password@postgres:5432/goplt?sslmode=disable"
|
|
||||||
REGISTRY_TYPE: consul
|
|
||||||
REGISTRY_CONSUL_ADDRESS: "consul:8500"
|
|
||||||
ports:
|
|
||||||
- "8084:8084"
|
|
||||||
depends_on:
|
|
||||||
postgres:
|
|
||||||
condition: service_healthy
|
|
||||||
consul:
|
|
||||||
condition: service_healthy
|
|
||||||
networks:
|
|
||||||
- goplt-network
|
|
||||||
restart: unless-stopped
|
|
||||||
|
|
||||||
api-gateway:
|
|
||||||
build:
|
|
||||||
context: .
|
|
||||||
dockerfile: cmd/api-gateway/Dockerfile
|
|
||||||
container_name: goplt-api-gateway
|
|
||||||
environment:
|
|
||||||
ENVIRONMENT: production
|
|
||||||
REGISTRY_TYPE: consul
|
|
||||||
REGISTRY_CONSUL_ADDRESS: "consul:8500"
|
|
||||||
GATEWAY_PORT: "8080"
|
|
||||||
GATEWAY_HOST: "0.0.0.0"
|
|
||||||
ports:
|
|
||||||
- "8080:8080"
|
|
||||||
depends_on:
|
|
||||||
consul:
|
|
||||||
condition: service_healthy
|
|
||||||
auth-service:
|
|
||||||
condition: service_started
|
|
||||||
identity-service:
|
|
||||||
condition: service_started
|
|
||||||
authz-service:
|
|
||||||
condition: service_started
|
|
||||||
audit-service:
|
|
||||||
condition: service_started
|
|
||||||
networks:
|
|
||||||
- goplt-network
|
|
||||||
restart: unless-stopped
|
|
||||||
|
|
||||||
volumes:
|
volumes:
|
||||||
postgres_data:
|
postgres_data:
|
||||||
driver: local
|
driver: local
|
||||||
consul_data:
|
|
||||||
driver: local
|
|
||||||
|
|
||||||
networks:
|
networks:
|
||||||
goplt-network:
|
goplt-network:
|
||||||
driver: bridge
|
driver: bridge
|
||||||
|
|
||||||
|
|||||||
@@ -1,11 +1,10 @@
|
|||||||
# ADR-0002: Go Version
|
# ADR-0002: Go Version
|
||||||
|
|
||||||
## Status
|
## Status
|
||||||
Superseded by [ADR-0034: Go Version Upgrade to 1.25.3](./0034-go-version-upgrade.md)
|
Accepted
|
||||||
|
|
||||||
## Context
|
## Context
|
||||||
Go releases new versions regularly with new features, performance improvements, and security fixes. We need to choose a Go version that:
|
Go releases new versions regularly with new features, performance improvements, and security fixes. We need to choose a Go version that:
|
||||||
|
|
||||||
- Provides necessary features for the platform
|
- Provides necessary features for the platform
|
||||||
- Has good ecosystem support
|
- Has good ecosystem support
|
||||||
- Is stable and production-ready
|
- Is stable and production-ready
|
||||||
|
|||||||
@@ -4,15 +4,13 @@
|
|||||||
Accepted
|
Accepted
|
||||||
|
|
||||||
## Context
|
## Context
|
||||||
The platform follows a microservices architecture where each service has its own database connection. The ORM/library must:
|
The platform needs a database ORM/library that:
|
||||||
|
- Supports PostgreSQL (primary database)
|
||||||
- Support PostgreSQL (primary database)
|
- Provides type-safe query building
|
||||||
- Provide type-safe query building
|
- Supports code generation (reduces boilerplate)
|
||||||
- Support code generation (reduces boilerplate)
|
- Handles migrations
|
||||||
- Handle migrations per service
|
- Supports relationships (many-to-many, etc.)
|
||||||
- Support relationships (many-to-many, etc.)
|
- Integrates with Ent (code generation)
|
||||||
- Integrate with Ent (code generation)
|
|
||||||
- Support schema isolation (each service owns its schema)
|
|
||||||
|
|
||||||
Options considered:
|
Options considered:
|
||||||
1. **entgo.io/ent** - Code-generated, type-safe ORM
|
1. **entgo.io/ent** - Code-generated, type-safe ORM
|
||||||
@@ -47,18 +45,10 @@ Use **entgo.io/ent** as the primary ORM for the platform.
|
|||||||
- Less flexible than raw SQL for complex queries
|
- Less flexible than raw SQL for complex queries
|
||||||
- Generated code must be committed or verified in CI
|
- Generated code must be committed or verified in CI
|
||||||
|
|
||||||
### Database Access Pattern
|
|
||||||
- **Each service has its own database connection pool**: Services do not share database connections
|
|
||||||
- **Schema isolation**: Each service owns its database schema (e.g., `auth_schema`, `identity_schema`, `blog_schema`)
|
|
||||||
- **No cross-service database access**: Services communicate via APIs, not direct database queries
|
|
||||||
- **Shared database instance**: Services share the same PostgreSQL instance but use different schemas
|
|
||||||
- **Alternative**: Database-per-service pattern (each service has its own database) for maximum isolation
|
|
||||||
|
|
||||||
### Implementation Notes
|
### Implementation Notes
|
||||||
- Install: `go get entgo.io/ent/cmd/ent`
|
- Install: `go get entgo.io/ent/cmd/ent`
|
||||||
- Each service initializes its own schema: `go run entgo.io/ent/cmd/ent init User Role Permission` (Identity Service)
|
- Initialize schema: `go run entgo.io/ent/cmd/ent init User Role Permission`
|
||||||
- Use `//go:generate` directives for code generation per service
|
- Use `//go:generate` directives for code generation
|
||||||
- Run migrations on startup via `client.Schema.Create()` for each service
|
- Run migrations on startup via `client.Schema.Create()`
|
||||||
- Create database client wrapper per service in `services/{service}/internal/database/client.go`
|
- Create wrapper in `internal/infra/database/client.go` for DI injection
|
||||||
- Each service manages its own connection pool configuration
|
|
||||||
|
|
||||||
|
|||||||
@@ -9,50 +9,31 @@ The platform needs to scale independently, support team autonomy, and enable fle
|
|||||||
## Decision
|
## Decision
|
||||||
Design the platform as **microservices architecture from day one**:
|
Design the platform as **microservices architecture from day one**:
|
||||||
|
|
||||||
1. **Core Services**: Core business services are separate microservices:
|
1. **Service-Based Architecture**: All modules are independent services:
|
||||||
|
- Each module is a separate service with its own process
|
||||||
- **Auth Service** (`cmd/auth-service/`): JWT token generation/validation
|
|
||||||
- **Identity Service** (`cmd/identity-service/`): User CRUD, password management
|
|
||||||
- **Authz Service** (`cmd/authz-service/`): Permission resolution, authorization
|
|
||||||
- **Audit Service** (`cmd/audit-service/`): Audit logging
|
|
||||||
- Each service has its own process, database connection, and deployment
|
|
||||||
|
|
||||||
2. **API Gateway**: Core infrastructure component (implemented in Epic 1):
|
|
||||||
- Single entry point for all external traffic
|
|
||||||
- Routes requests to backend services via service discovery
|
|
||||||
- Handles authentication, rate limiting, CORS at the edge
|
|
||||||
- Not optional - required for microservices architecture
|
|
||||||
|
|
||||||
3. **Service-Based Architecture**: All modules are independent services:
|
|
||||||
- Each module/service is a separate service with its own process
|
|
||||||
- Services communicate via gRPC (primary) or HTTP (fallback)
|
- Services communicate via gRPC (primary) or HTTP (fallback)
|
||||||
- Service client interfaces for all inter-service communication
|
- Service client interfaces for all inter-service communication
|
||||||
- No direct in-process calls between services
|
- No direct in-process calls between services
|
||||||
|
|
||||||
4. **Service Registry**: Central registry for service discovery:
|
2. **Service Registry**: Central registry for service discovery:
|
||||||
- All services register on startup
|
- All services register on startup
|
||||||
- Service discovery via registry
|
- Service discovery via registry
|
||||||
- Health checking and automatic deregistration
|
- Health checking and automatic deregistration
|
||||||
- Support for Consul, etcd, or Kubernetes service discovery
|
- Support for Consul, etcd, or Kubernetes service discovery
|
||||||
|
|
||||||
5. **Communication Patterns**:
|
3. **Communication Patterns**:
|
||||||
- **Synchronous**: gRPC service calls (primary), HTTP/REST (fallback)
|
- **Synchronous**: gRPC service calls (primary), HTTP/REST (fallback)
|
||||||
- **Asynchronous**: Event bus via Kafka
|
- **Asynchronous**: Event bus via Kafka
|
||||||
- **Shared Infrastructure**: Cache (Redis) and Database (PostgreSQL instance)
|
- **Shared State**: Cache (Redis) and Database (PostgreSQL)
|
||||||
- **Database Access**: Each service has its own connection pool and schema
|
|
||||||
|
|
||||||
6. **Service Boundaries**: Each service is independent:
|
4. **Service Boundaries**: Each module is an independent service:
|
||||||
- Independent Go modules (`go.mod`)
|
- Independent Go modules (`go.mod`)
|
||||||
- Own database schema (via Ent) - schema isolation
|
- Own database schema (via Ent)
|
||||||
- Own API routes (gRPC/HTTP)
|
- Own API routes
|
||||||
- Own process and deployment
|
- Own process and deployment
|
||||||
- Can be scaled independently
|
- Can be scaled independently
|
||||||
|
|
||||||
7. **Development Mode**: For local development, services run in the same repository:
|
5. **Development Simplification**: For local development, multiple services can run in the same process, but they still communicate via service clients (no direct calls)
|
||||||
- Each service has its own entry point and process
|
|
||||||
- Services still communicate via service clients (gRPC/HTTP)
|
|
||||||
- No direct in-process calls
|
|
||||||
- Docker Compose for easy local setup
|
|
||||||
|
|
||||||
## Consequences
|
## Consequences
|
||||||
|
|
||||||
@@ -74,36 +55,29 @@ Design the platform as **microservices architecture from day one**:
|
|||||||
- **Development Setup**: More complex local development (multiple services)
|
- **Development Setup**: More complex local development (multiple services)
|
||||||
|
|
||||||
### Mitigations
|
### Mitigations
|
||||||
- **API Gateway**: Implemented in Epic 1 as core infrastructure - handles routing, authentication, rate limiting
|
- **Service Mesh**: Use service mesh (Istio, Linkerd) for advanced microservices features
|
||||||
- **Service Mesh**: Use service mesh (Istio, Linkerd) for advanced microservices features (optional)
|
- **API Gateway**: Central gateway for routing and cross-cutting concerns
|
||||||
- **Event Sourcing**: Use events for eventual consistency
|
- **Event Sourcing**: Use events for eventual consistency
|
||||||
- **Circuit Breakers**: Implement circuit breakers for resilience
|
- **Circuit Breakers**: Implement circuit breakers for resilience
|
||||||
- **Comprehensive Observability**: OpenTelemetry, metrics, logging essential
|
- **Comprehensive Observability**: OpenTelemetry, metrics, logging essential
|
||||||
- **Docker Compose**: Simplify local development with docker-compose
|
- **Docker Compose**: Simplify local development with docker-compose
|
||||||
- **Service Clients**: All inter-service communication via service clients (gRPC/HTTP)
|
- **Development Mode**: Run multiple services in same process for local dev (still use service clients)
|
||||||
|
|
||||||
## Implementation Strategy
|
## Implementation Strategy
|
||||||
|
|
||||||
### Epic 1: Core Kernel & Infrastructure
|
### Epic 1: Service Client Interfaces (Epic 1)
|
||||||
- Core kernel (infrastructure only): config, logger, DI, health, metrics, observability
|
- Define service client interfaces for all core services
|
||||||
- API Gateway implementation (core infrastructure component)
|
- All inter-service communication goes through interfaces
|
||||||
- Service client interfaces for all core services
|
|
||||||
- Service registry interface and basic implementation
|
|
||||||
|
|
||||||
### Epic 2: Core Services Separation
|
### Epic 2: Service Registry (Epic 3)
|
||||||
- Separate Auth, Identity, Authz, Audit into independent services
|
- Create service registry interface
|
||||||
- Each service: own entry point (`cmd/{service}/`), gRPC server, database connection
|
- Implement service discovery
|
||||||
- Service client implementations (gRPC/HTTP)
|
- Support for Consul, Kubernetes service discovery
|
||||||
- Service registration with registry
|
|
||||||
|
|
||||||
### Epic 3: Service Registry & Discovery (Epic 3)
|
### Epic 3: gRPC Services (Epic 5)
|
||||||
- Complete service registry implementation
|
- Implement gRPC service definitions
|
||||||
- Service discovery (Consul, Kubernetes)
|
- Create gRPC servers for all services
|
||||||
- Service health checking and deregistration
|
- Create gRPC clients for service communication
|
||||||
|
|
||||||
### Epic 5: gRPC Services (Epic 5)
|
|
||||||
- Complete gRPC service definitions for all services
|
|
||||||
- gRPC clients for service communication
|
|
||||||
- HTTP clients as fallback option
|
- HTTP clients as fallback option
|
||||||
|
|
||||||
## References
|
## References
|
||||||
|
|||||||
@@ -7,30 +7,22 @@ Accepted
|
|||||||
Services need to communicate with each other in a microservices architecture. All communication must go through well-defined interfaces that support network calls.
|
Services need to communicate with each other in a microservices architecture. All communication must go through well-defined interfaces that support network calls.
|
||||||
|
|
||||||
## Decision
|
## Decision
|
||||||
Use a **service client-based communication strategy** with API Gateway as the entry point:
|
Use a **service client-based communication strategy**:
|
||||||
|
|
||||||
1. **API Gateway** (Entry Point):
|
1. **Service Client Interfaces** (Primary for synchronous calls):
|
||||||
- All external traffic enters through API Gateway
|
|
||||||
- Gateway routes requests to backend services via service discovery
|
|
||||||
- Gateway handles authentication (JWT validation via Auth Service)
|
|
||||||
- Gateway handles rate limiting, CORS, request transformation
|
|
||||||
|
|
||||||
2. **Service Client Interfaces** (Primary for synchronous calls):
|
|
||||||
- Define interfaces in `pkg/services/` for all services
|
- Define interfaces in `pkg/services/` for all services
|
||||||
- All implementations are network-based:
|
- All implementations are network-based:
|
||||||
- `internal/services/grpc/client/` - gRPC clients (primary)
|
- `internal/services/grpc/client/` - gRPC clients (primary)
|
||||||
- `internal/services/http/client/` - HTTP clients (fallback)
|
- `internal/services/http/client/` - HTTP clients (fallback)
|
||||||
- Gateway uses service clients to communicate with backend services
|
|
||||||
- Services use service clients for inter-service communication
|
|
||||||
|
|
||||||
3. **Event Bus** (Primary for asynchronous communication):
|
2. **Event Bus** (Primary for asynchronous communication):
|
||||||
- Distributed via Kafka
|
- Distributed via Kafka
|
||||||
- Preferred for cross-service communication
|
- Preferred for cross-service communication
|
||||||
- Event-driven architecture for loose coupling
|
- Event-driven architecture for loose coupling
|
||||||
|
|
||||||
4. **Shared Infrastructure** (For state):
|
3. **Shared Infrastructure** (For state):
|
||||||
- Redis for cache and distributed state
|
- Redis for cache and distributed state
|
||||||
- PostgreSQL instance for persistent data (each service has its own schema)
|
- PostgreSQL for persistent data
|
||||||
- Kafka for events
|
- Kafka for events
|
||||||
|
|
||||||
## Service Client Pattern
|
## Service Client Pattern
|
||||||
@@ -55,22 +47,8 @@ type httpIdentityClient struct {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
## Communication Flow
|
|
||||||
|
|
||||||
```
|
|
||||||
Client → API Gateway → Backend Service (via service client)
|
|
||||||
Backend Service → Other Service (via service client)
|
|
||||||
```
|
|
||||||
|
|
||||||
All communication goes through service clients - no direct in-process calls even in development mode.
|
|
||||||
|
|
||||||
## Development Mode
|
## Development Mode
|
||||||
For local development, services run in the same repository but as separate processes:
|
For local development, multiple services can run in the same process, but they still communicate via service clients (gRPC or HTTP) - no direct in-process calls. This ensures the architecture is consistent.
|
||||||
|
|
||||||
- Each service has its own entry point (`cmd/{service}/`)
|
|
||||||
- Services communicate via service clients (gRPC or HTTP) - no direct in-process calls
|
|
||||||
- Docker Compose orchestrates all services
|
|
||||||
- This ensures the architecture is consistent with production
|
|
||||||
|
|
||||||
## Consequences
|
## Consequences
|
||||||
|
|
||||||
|
|||||||
@@ -10,7 +10,6 @@ The platform follows a microservices architecture where each service (Auth, Iden
|
|||||||
- **Option 2**: Separate repositories (each service in its own repository)
|
- **Option 2**: Separate repositories (each service in its own repository)
|
||||||
|
|
||||||
The decision affects:
|
The decision affects:
|
||||||
|
|
||||||
- Code sharing and dependencies
|
- Code sharing and dependencies
|
||||||
- Development workflow
|
- Development workflow
|
||||||
- CI/CD complexity
|
- CI/CD complexity
|
||||||
@@ -31,13 +30,12 @@ Use a **monorepo structure with service directories** for all services:
|
|||||||
```
|
```
|
||||||
goplt/
|
goplt/
|
||||||
├── cmd/
|
├── cmd/
|
||||||
│ ├── platform/ # Core kernel entry point (minimal, infrastructure only)
|
│ ├── platform/ # Core kernel entry point
|
||||||
│ ├── api-gateway/ # API Gateway service entry point
|
│ ├── auth-service/ # Auth Service entry point
|
||||||
│ ├── auth-service/ # Auth Service entry point
|
│ ├── identity-service/ # Identity Service entry point
|
||||||
│ ├── identity-service/ # Identity Service entry point
|
│ ├── authz-service/ # Authz Service entry point
|
||||||
│ ├── authz-service/ # Authz Service entry point
|
│ ├── audit-service/ # Audit Service entry point
|
||||||
│ ├── audit-service/ # Audit Service entry point
|
│ └── blog-service/ # Blog module service entry point
|
||||||
│ └── blog-service/ # Blog feature service entry point
|
|
||||||
├── services/ # Service implementations (optional alternative)
|
├── services/ # Service implementations (optional alternative)
|
||||||
│ ├── auth/
|
│ ├── auth/
|
||||||
│ │ ├── internal/ # Service implementation
|
│ │ ├── internal/ # Service implementation
|
||||||
@@ -147,22 +145,17 @@ Use a **monorepo structure with service directories** for all services:
|
|||||||
- Single entry point `cmd/platform/`
|
- Single entry point `cmd/platform/`
|
||||||
- Shared infrastructure established
|
- Shared infrastructure established
|
||||||
|
|
||||||
### Phase 2: Service Structure (Epic 1-2)
|
### Phase 2: Service Structure (Epic 2)
|
||||||
- **Epic 1**: Create API Gateway service:
|
- Create service directories in `cmd/`:
|
||||||
- `cmd/api-gateway/` - API Gateway entry point
|
- `cmd/auth-service/`
|
||||||
- Service discovery integration
|
- `cmd/identity-service/`
|
||||||
- Request routing to backend services
|
- `cmd/authz-service/`
|
||||||
- **Epic 2**: Create core service directories:
|
- `cmd/audit-service/`
|
||||||
- `cmd/auth-service/` - Auth Service entry point
|
|
||||||
- `cmd/identity-service/` - Identity Service entry point
|
|
||||||
- `cmd/authz-service/` - Authz Service entry point
|
|
||||||
- `cmd/audit-service/` - Audit Service entry point
|
|
||||||
- Create service implementations:
|
- Create service implementations:
|
||||||
- Option A: `services/{service}/internal/` for each service (recommended)
|
- Option A: `services/{service}/internal/` for each service
|
||||||
- Option B: `internal/{service}/` for each service
|
- Option B: `internal/{service}/` for each service (if keeping all in internal/)
|
||||||
- Each service has its own database connection pool
|
- Option C: Service code directly in `cmd/{service}/` for simple services
|
||||||
- Define service client interfaces in `pkg/services/`:
|
- Define service client interfaces in `pkg/services/`
|
||||||
- `AuthServiceClient`, `IdentityServiceClient`, `AuthzServiceClient`, `AuditServiceClient`
|
|
||||||
- Implement gRPC/HTTP clients in `internal/services/`
|
- Implement gRPC/HTTP clients in `internal/services/`
|
||||||
|
|
||||||
### Phase 3: Module Services (Epic 4+)
|
### Phase 3: Module Services (Epic 4+)
|
||||||
@@ -177,27 +170,18 @@ Use a **monorepo structure with service directories** for all services:
|
|||||||
```
|
```
|
||||||
goplt/
|
goplt/
|
||||||
├── cmd/
|
├── cmd/
|
||||||
│ ├── platform/ # Core kernel (minimal, infrastructure only)
|
│ ├── platform/ # Core kernel
|
||||||
│ ├── api-gateway/ # API Gateway entry point
|
|
||||||
│ ├── auth-service/ # Auth entry point
|
│ ├── auth-service/ # Auth entry point
|
||||||
│ ├── identity-service/ # Identity entry point
|
│ ├── identity-service/ # Identity entry point
|
||||||
│ ├── authz-service/ # Authz entry point
|
│ └── ...
|
||||||
│ ├── audit-service/ # Audit entry point
|
|
||||||
│ └── blog-service/ # Blog feature service entry point
|
|
||||||
├── services/ # Service implementations
|
├── services/ # Service implementations
|
||||||
│ ├── gateway/
|
|
||||||
│ │ ├── internal/ # Gateway implementation
|
|
||||||
│ │ └── api/ # Routing logic
|
|
||||||
│ ├── auth/
|
│ ├── auth/
|
||||||
│ │ ├── internal/ # Service implementation
|
│ │ ├── internal/ # Service implementation
|
||||||
│ │ └── api/ # gRPC/HTTP definitions
|
│ │ └── api/ # gRPC/HTTP definitions
|
||||||
│ ├── identity/
|
│ └── ...
|
||||||
│ ├── authz/
|
├── internal/ # Core kernel (shared)
|
||||||
│ ├── audit/
|
|
||||||
│ └── blog/
|
|
||||||
├── internal/ # Core kernel (shared infrastructure)
|
|
||||||
├── pkg/ # Public interfaces
|
├── pkg/ # Public interfaces
|
||||||
└── modules/ # Feature modules (optional structure)
|
└── modules/ # Feature modules
|
||||||
```
|
```
|
||||||
|
|
||||||
This provides:
|
This provides:
|
||||||
|
|||||||
@@ -1,167 +0,0 @@
|
|||||||
# ADR-0032: API Gateway Strategy
|
|
||||||
|
|
||||||
## Status
|
|
||||||
Accepted
|
|
||||||
|
|
||||||
## Context
|
|
||||||
The platform follows a microservices architecture where each service is independently deployable. We need a central entry point that handles:
|
|
||||||
|
|
||||||
- Request routing to backend services
|
|
||||||
- Authentication and authorization at the edge
|
|
||||||
- Rate limiting and throttling
|
|
||||||
- CORS and request/response transformation
|
|
||||||
- Service discovery integration
|
|
||||||
|
|
||||||
Options considered:
|
|
||||||
1. **Custom API Gateway** - Build our own gateway service
|
|
||||||
2. **Kong** - Open-source API Gateway
|
|
||||||
3. **Envoy** - High-performance proxy
|
|
||||||
4. **Traefik** - Modern reverse proxy
|
|
||||||
|
|
||||||
## Decision
|
|
||||||
Implement a **custom API Gateway service** as a core infrastructure component in Epic 1:
|
|
||||||
|
|
||||||
1. **API Gateway as Core Component**:
|
|
||||||
- Entry point: `cmd/api-gateway/`
|
|
||||||
- Implementation: `services/gateway/internal/`
|
|
||||||
- Implemented in Epic 1 (not deferred to Epic 8)
|
|
||||||
- Required for microservices architecture, not optional
|
|
||||||
|
|
||||||
2. **Responsibilities**:
|
|
||||||
- **Request Routing**: Route requests to backend services via service discovery
|
|
||||||
- **Authentication**: Validate JWT tokens via Auth Service
|
|
||||||
- **Authorization**: Check permissions via Authz Service (for route-level auth)
|
|
||||||
- **Rate Limiting**: Per-user and per-IP rate limiting
|
|
||||||
- **CORS**: Handle cross-origin requests
|
|
||||||
- **Request Transformation**: Modify requests before forwarding
|
|
||||||
- **Response Transformation**: Modify responses before returning
|
|
||||||
- **Load Balancing**: Distribute requests across service instances
|
|
||||||
|
|
||||||
3. **Integration Points**:
|
|
||||||
- Service registry for service discovery
|
|
||||||
- Auth Service client for token validation
|
|
||||||
- Authz Service client for permission checks
|
|
||||||
- Cache (Redis) for rate limiting state
|
|
||||||
|
|
||||||
4. **Implementation Approach**:
|
|
||||||
- Built with Go (Gin/Echo framework)
|
|
||||||
- Uses service clients for backend communication
|
|
||||||
- Configurable routing rules
|
|
||||||
- Middleware-based architecture
|
|
||||||
|
|
||||||
## Architecture
|
|
||||||
|
|
||||||
```mermaid
|
|
||||||
graph TB
|
|
||||||
Client[Client] --> Gateway[API Gateway<br/>:8080]
|
|
||||||
|
|
||||||
Gateway --> AuthClient[Auth Service Client]
|
|
||||||
Gateway --> AuthzClient[Authz Service Client]
|
|
||||||
Gateway --> ServiceRegistry[Service Registry]
|
|
||||||
Gateway --> Cache[Cache<br/>Rate Limiting]
|
|
||||||
|
|
||||||
AuthClient --> AuthSvc[Auth Service<br/>:8081]
|
|
||||||
AuthzClient --> AuthzSvc[Authz Service<br/>:8083]
|
|
||||||
ServiceRegistry --> BackendSvc[Backend Services]
|
|
||||||
|
|
||||||
Gateway --> BackendSvc
|
|
||||||
|
|
||||||
style Gateway fill:#4a90e2,stroke:#2e5c8a,stroke-width:3px,color:#fff
|
|
||||||
style AuthSvc fill:#ff6b6b,stroke:#c92a2a,stroke-width:2px,color:#fff
|
|
||||||
style BackendSvc fill:#7b68ee,stroke:#5a4fcf,stroke-width:2px,color:#fff
|
|
||||||
```
|
|
||||||
|
|
||||||
## Request Flow
|
|
||||||
|
|
||||||
```mermaid
|
|
||||||
sequenceDiagram
|
|
||||||
participant Client
|
|
||||||
participant Gateway
|
|
||||||
participant AuthSvc
|
|
||||||
participant AuthzSvc
|
|
||||||
participant Registry
|
|
||||||
participant BackendSvc
|
|
||||||
|
|
||||||
Client->>Gateway: HTTP Request
|
|
||||||
Gateway->>Gateway: Rate limiting check
|
|
||||||
Gateway->>AuthSvc: Validate JWT (gRPC)
|
|
||||||
AuthSvc-->>Gateway: Token valid + user info
|
|
||||||
Gateway->>AuthzSvc: Check route permission (gRPC, optional)
|
|
||||||
AuthzSvc-->>Gateway: Authorized
|
|
||||||
Gateway->>Registry: Discover backend service
|
|
||||||
Registry-->>Gateway: Service endpoint
|
|
||||||
Gateway->>BackendSvc: Forward request (gRPC/HTTP)
|
|
||||||
BackendSvc-->>Gateway: Response
|
|
||||||
Gateway-->>Client: HTTP Response
|
|
||||||
```
|
|
||||||
|
|
||||||
## Consequences
|
|
||||||
|
|
||||||
### Positive
|
|
||||||
- **Single Entry Point**: All external traffic goes through one gateway
|
|
||||||
- **Centralized Security**: Authentication and authorization at the edge
|
|
||||||
- **Performance**: Rate limiting and caching at gateway level
|
|
||||||
- **Flexibility**: Easy to add new routes and services
|
|
||||||
- **Consistency**: Uniform API interface for clients
|
|
||||||
- **Observability**: Central point for metrics and logging
|
|
||||||
|
|
||||||
### Negative
|
|
||||||
- **Single Point of Failure**: Gateway failure affects all traffic
|
|
||||||
- **Additional Latency**: Extra hop in request path
|
|
||||||
- **Complexity**: Additional service to maintain and deploy
|
|
||||||
- **Scaling**: Gateway must scale to handle all traffic
|
|
||||||
|
|
||||||
### Mitigations
|
|
||||||
1. **High Availability**: Deploy multiple gateway instances behind load balancer
|
|
||||||
2. **Circuit Breakers**: Implement circuit breakers for backend service failures
|
|
||||||
3. **Caching**: Cache authentication results and service endpoints
|
|
||||||
4. **Monitoring**: Comprehensive monitoring and alerting
|
|
||||||
5. **Graceful Degradation**: Fallback mechanisms for service failures
|
|
||||||
|
|
||||||
## Implementation Strategy
|
|
||||||
|
|
||||||
### Epic 1: Core Infrastructure
|
|
||||||
- Create `cmd/api-gateway/` entry point
|
|
||||||
- Implement basic routing with service discovery
|
|
||||||
- JWT validation via Auth Service client
|
|
||||||
- Rate limiting middleware
|
|
||||||
- CORS support
|
|
||||||
|
|
||||||
### Epic 2-3: Enhanced Features
|
|
||||||
- Permission-based routing (via Authz Service)
|
|
||||||
- Request/response transformation
|
|
||||||
- Advanced load balancing
|
|
||||||
- Health check integration
|
|
||||||
|
|
||||||
## Configuration
|
|
||||||
|
|
||||||
```yaml
|
|
||||||
gateway:
|
|
||||||
port: 8080
|
|
||||||
routes:
|
|
||||||
- path: /api/v1/auth/**
|
|
||||||
service: auth-service
|
|
||||||
auth_required: false
|
|
||||||
- path: /api/v1/users/**
|
|
||||||
service: identity-service
|
|
||||||
auth_required: true
|
|
||||||
permission: user.read
|
|
||||||
- path: /api/v1/blog/**
|
|
||||||
service: blog-service
|
|
||||||
auth_required: true
|
|
||||||
permission: blog.post.read
|
|
||||||
rate_limiting:
|
|
||||||
enabled: true
|
|
||||||
per_user: 100/minute
|
|
||||||
per_ip: 1000/minute
|
|
||||||
cors:
|
|
||||||
allowed_origins: ["*"]
|
|
||||||
allowed_methods: ["GET", "POST", "PUT", "DELETE"]
|
|
||||||
```
|
|
||||||
|
|
||||||
## References
|
|
||||||
- [ADR-0029: Microservices Architecture](./0029-microservices-architecture.md)
|
|
||||||
- [ADR-0030: Service Communication Strategy](./0030-service-communication-strategy.md)
|
|
||||||
- [ADR-0031: Service Repository Structure](./0031-service-repository-structure.md)
|
|
||||||
- [API Gateway Pattern](https://microservices.io/patterns/apigateway.html)
|
|
||||||
|
|
||||||
@@ -1,310 +0,0 @@
|
|||||||
# ADR-0033: Service Discovery Implementation
|
|
||||||
|
|
||||||
## Status
|
|
||||||
Accepted
|
|
||||||
|
|
||||||
## Context
|
|
||||||
The platform follows a microservices architecture where services need to discover and communicate with each other. We need a service discovery mechanism that:
|
|
||||||
|
|
||||||
- Enables services to find each other dynamically
|
|
||||||
- Supports health checking and automatic deregistration
|
|
||||||
- Works in both development (Docker Compose) and production (Kubernetes) environments
|
|
||||||
- Provides service registration and discovery APIs
|
|
||||||
- Supports multiple service instances (load balancing)
|
|
||||||
|
|
||||||
Options considered:
|
|
||||||
1. **Consul** - HashiCorp's service discovery and configuration tool
|
|
||||||
2. **etcd** - Distributed key-value store with service discovery
|
|
||||||
3. **Kubernetes Service Discovery** - Native K8s service discovery
|
|
||||||
4. **Eureka** - Netflix service discovery (Java-focused)
|
|
||||||
5. **Custom Registry** - Build our own service registry
|
|
||||||
|
|
||||||
## Decision
|
|
||||||
Use **Consul** as the primary service discovery implementation with support for Kubernetes service discovery as an alternative.
|
|
||||||
|
|
||||||
### Rationale
|
|
||||||
|
|
||||||
1. **Mature and Production-Ready**:
|
|
||||||
- Battle-tested in production environments
|
|
||||||
- Active development and strong community
|
|
||||||
- Comprehensive documentation
|
|
||||||
|
|
||||||
2. **Feature-Rich**:
|
|
||||||
- Service registration and health checking
|
|
||||||
- Key-value store for configuration
|
|
||||||
- Service mesh capabilities (Consul Connect)
|
|
||||||
- Multi-datacenter support
|
|
||||||
- DNS-based service discovery
|
|
||||||
|
|
||||||
3. **Development-Friendly**:
|
|
||||||
- Easy to run locally (single binary or Docker)
|
|
||||||
- Docker Compose integration
|
|
||||||
- Good for local development setup
|
|
||||||
|
|
||||||
4. **Production-Ready**:
|
|
||||||
- Works well in Kubernetes (Consul K8s)
|
|
||||||
- Can be used alongside Kubernetes service discovery
|
|
||||||
- Supports high availability and clustering
|
|
||||||
|
|
||||||
5. **Language Agnostic**:
|
|
||||||
- HTTP API for service registration
|
|
||||||
- gRPC support
|
|
||||||
- Go client library available
|
|
||||||
|
|
||||||
6. **Health Checking**:
|
|
||||||
- Built-in health checking with automatic deregistration
|
|
||||||
- Multiple health check types (HTTP, TCP, gRPC, script)
|
|
||||||
- Health status propagation
|
|
||||||
|
|
||||||
## Architecture
|
|
||||||
|
|
||||||
### Service Registry Interface
|
|
||||||
|
|
||||||
```go
|
|
||||||
// pkg/registry/registry.go
|
|
||||||
type ServiceRegistry interface {
|
|
||||||
// Register a service instance
|
|
||||||
Register(ctx context.Context, service *ServiceInstance) error
|
|
||||||
|
|
||||||
// Deregister a service instance
|
|
||||||
Deregister(ctx context.Context, serviceID string) error
|
|
||||||
|
|
||||||
// Discover service instances
|
|
||||||
Discover(ctx context.Context, serviceName string) ([]*ServiceInstance, error)
|
|
||||||
|
|
||||||
// Watch for service changes
|
|
||||||
Watch(ctx context.Context, serviceName string) (<-chan []*ServiceInstance, error)
|
|
||||||
|
|
||||||
// Get service health
|
|
||||||
Health(ctx context.Context, serviceID string) (*HealthStatus, error)
|
|
||||||
}
|
|
||||||
|
|
||||||
type ServiceInstance struct {
|
|
||||||
ID string
|
|
||||||
Name string
|
|
||||||
Address string
|
|
||||||
Port int
|
|
||||||
Tags []string
|
|
||||||
Metadata map[string]string
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Consul Implementation
|
|
||||||
|
|
||||||
```go
|
|
||||||
// internal/registry/consul/consul.go
|
|
||||||
type ConsulRegistry struct {
|
|
||||||
client *consul.Client
|
|
||||||
config *ConsulConfig
|
|
||||||
}
|
|
||||||
|
|
||||||
// Register service with Consul
|
|
||||||
func (r *ConsulRegistry) Register(ctx context.Context, service *ServiceInstance) error {
|
|
||||||
registration := &consul.AgentServiceRegistration{
|
|
||||||
ID: service.ID,
|
|
||||||
Name: service.Name,
|
|
||||||
Address: service.Address,
|
|
||||||
Port: service.Port,
|
|
||||||
Tags: service.Tags,
|
|
||||||
Meta: service.Metadata,
|
|
||||||
Check: &consul.AgentServiceCheck{
|
|
||||||
HTTP: fmt.Sprintf("http://%s:%d/healthz", service.Address, service.Port),
|
|
||||||
Interval: "10s",
|
|
||||||
Timeout: "3s",
|
|
||||||
DeregisterCriticalServiceAfter: "30s",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
return r.client.Agent().ServiceRegister(registration)
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Implementation Strategy
|
|
||||||
|
|
||||||
### Phase 1: Consul Implementation (Epic 1)
|
|
||||||
- Create service registry interface in `pkg/registry/`
|
|
||||||
- Implement Consul registry in `internal/registry/consul/`
|
|
||||||
- Basic service registration and discovery
|
|
||||||
- Health check integration
|
|
||||||
|
|
||||||
### Phase 2: Kubernetes Support (Epic 6)
|
|
||||||
- Implement Kubernetes service discovery as alternative
|
|
||||||
- Service registry factory that selects implementation based on environment
|
|
||||||
- Support for both Consul and K8s in same codebase
|
|
||||||
|
|
||||||
### Phase 3: Advanced Features (Epic 6)
|
|
||||||
- Service mesh integration (Consul Connect)
|
|
||||||
- Multi-datacenter support
|
|
||||||
- Service tags and filtering
|
|
||||||
- Service metadata and configuration
|
|
||||||
|
|
||||||
## Configuration
|
|
||||||
|
|
||||||
```yaml
|
|
||||||
registry:
|
|
||||||
type: consul # or "kubernetes"
|
|
||||||
consul:
|
|
||||||
address: "localhost:8500"
|
|
||||||
datacenter: "dc1"
|
|
||||||
scheme: "http"
|
|
||||||
health_check:
|
|
||||||
interval: "10s"
|
|
||||||
timeout: "3s"
|
|
||||||
deregister_after: "30s"
|
|
||||||
kubernetes:
|
|
||||||
namespace: "default"
|
|
||||||
in_cluster: true
|
|
||||||
```
|
|
||||||
|
|
||||||
## Service Registration Flow
|
|
||||||
|
|
||||||
```mermaid
|
|
||||||
sequenceDiagram
|
|
||||||
participant Service
|
|
||||||
participant Registry[Service Registry Interface]
|
|
||||||
participant Consul
|
|
||||||
participant Health[Health Check]
|
|
||||||
|
|
||||||
Service->>Registry: Register(serviceInstance)
|
|
||||||
Registry->>Consul: Register service
|
|
||||||
Consul->>Consul: Store service info
|
|
||||||
Consul->>Health: Start health checks
|
|
||||||
|
|
||||||
loop Health Check
|
|
||||||
Health->>Service: GET /healthz
|
|
||||||
Service-->>Health: 200 OK
|
|
||||||
Health->>Consul: Update health status
|
|
||||||
end
|
|
||||||
|
|
||||||
Service->>Registry: Deregister(serviceID)
|
|
||||||
Registry->>Consul: Deregister service
|
|
||||||
Consul->>Consul: Remove service
|
|
||||||
```
|
|
||||||
|
|
||||||
## Service Discovery Flow
|
|
||||||
|
|
||||||
```mermaid
|
|
||||||
sequenceDiagram
|
|
||||||
participant Client
|
|
||||||
participant Registry[Service Registry]
|
|
||||||
participant Consul
|
|
||||||
participant Service1[Service Instance 1]
|
|
||||||
participant Service2[Service Instance 2]
|
|
||||||
|
|
||||||
Client->>Registry: Discover("auth-service")
|
|
||||||
Registry->>Consul: Query service instances
|
|
||||||
Consul-->>Registry: [instance1, instance2]
|
|
||||||
Registry->>Registry: Filter healthy instances
|
|
||||||
Registry-->>Client: [healthy instances]
|
|
||||||
|
|
||||||
Client->>Service1: gRPC call
|
|
||||||
Service1-->>Client: Response
|
|
||||||
```
|
|
||||||
|
|
||||||
## Development Setup
|
|
||||||
|
|
||||||
### Docker Compose
|
|
||||||
|
|
||||||
```yaml
|
|
||||||
services:
|
|
||||||
consul:
|
|
||||||
image: consul:latest
|
|
||||||
ports:
|
|
||||||
- "8500:8500"
|
|
||||||
command: consul agent -dev -client=0.0.0.0
|
|
||||||
volumes:
|
|
||||||
- consul-data:/consul/data
|
|
||||||
|
|
||||||
volumes:
|
|
||||||
consul-data:
|
|
||||||
```
|
|
||||||
|
|
||||||
### Local Development
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Run Consul in dev mode
|
|
||||||
consul agent -dev
|
|
||||||
|
|
||||||
# Or use Docker
|
|
||||||
docker run -d --name consul -p 8500:8500 consul:latest
|
|
||||||
```
|
|
||||||
|
|
||||||
## Production Deployment
|
|
||||||
|
|
||||||
### Kubernetes
|
|
||||||
|
|
||||||
```yaml
|
|
||||||
# Consul Helm Chart
|
|
||||||
helm repo add hashicorp https://helm.releases.hashicorp.com
|
|
||||||
helm install consul hashicorp/consul --set global.datacenter=dc1
|
|
||||||
```
|
|
||||||
|
|
||||||
### Standalone Cluster
|
|
||||||
|
|
||||||
- Deploy Consul cluster (3-5 nodes)
|
|
||||||
- Configure service discovery endpoints
|
|
||||||
- Set up Consul Connect for service mesh (optional)
|
|
||||||
|
|
||||||
## Consequences
|
|
||||||
|
|
||||||
### Positive
|
|
||||||
- **Dynamic Service Discovery**: Services can be added/removed without configuration changes
|
|
||||||
- **Health Checking**: Automatic removal of unhealthy services
|
|
||||||
- **Load Balancing**: Multiple service instances automatically discovered
|
|
||||||
- **Configuration Management**: Consul KV store for service configuration
|
|
||||||
- **Service Mesh Ready**: Can use Consul Connect for advanced features
|
|
||||||
- **Development Friendly**: Easy local setup with Docker
|
|
||||||
|
|
||||||
### Negative
|
|
||||||
- **Additional Infrastructure**: Requires Consul cluster in production
|
|
||||||
- **Network Dependency**: Services depend on Consul availability
|
|
||||||
- **Configuration Complexity**: Need to configure Consul cluster
|
|
||||||
- **Learning Curve**: Team needs to understand Consul concepts
|
|
||||||
|
|
||||||
### Mitigations
|
|
||||||
1. **High Availability**: Deploy Consul cluster (3+ nodes)
|
|
||||||
2. **Caching**: Cache service instances to reduce Consul queries
|
|
||||||
3. **Fallback**: Support Kubernetes service discovery as fallback
|
|
||||||
4. **Documentation**: Comprehensive setup and usage documentation
|
|
||||||
5. **Monitoring**: Monitor Consul health and service registration
|
|
||||||
|
|
||||||
## Alternative: Kubernetes Service Discovery
|
|
||||||
|
|
||||||
For Kubernetes deployments, we also support native Kubernetes service discovery:
|
|
||||||
|
|
||||||
```go
|
|
||||||
// internal/registry/kubernetes/k8s.go
|
|
||||||
type KubernetesRegistry struct {
|
|
||||||
clientset kubernetes.Interface
|
|
||||||
namespace string
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *KubernetesRegistry) Discover(ctx context.Context, serviceName string) ([]*ServiceInstance, error) {
|
|
||||||
endpoints, err := r.clientset.CoreV1().Endpoints(r.namespace).Get(ctx, serviceName, metav1.GetOptions{})
|
|
||||||
// Convert K8s endpoints to ServiceInstance
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Service Registry Factory
|
|
||||||
|
|
||||||
```go
|
|
||||||
// internal/registry/factory.go
|
|
||||||
func NewServiceRegistry(cfg *config.Config) (registry.ServiceRegistry, error) {
|
|
||||||
switch cfg.Registry.Type {
|
|
||||||
case "consul":
|
|
||||||
return consul.NewRegistry(cfg.Registry.Consul)
|
|
||||||
case "kubernetes":
|
|
||||||
return kubernetes.NewRegistry(cfg.Registry.Kubernetes)
|
|
||||||
default:
|
|
||||||
return nil, fmt.Errorf("unknown registry type: %s", cfg.Registry.Type)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## References
|
|
||||||
- [ADR-0029: Microservices Architecture](./0029-microservices-architecture.md)
|
|
||||||
- [ADR-0030: Service Communication Strategy](./0030-service-communication-strategy.md)
|
|
||||||
- [ADR-0031: Service Repository Structure](./0031-service-repository-structure.md)
|
|
||||||
- [Consul Documentation](https://www.consul.io/docs)
|
|
||||||
- [Consul Go Client](https://github.com/hashicorp/consul/api)
|
|
||||||
- [Consul Kubernetes](https://www.consul.io/docs/k8s)
|
|
||||||
|
|
||||||
@@ -1,57 +0,0 @@
|
|||||||
# ADR-0034: Go Version Upgrade to 1.25.3
|
|
||||||
|
|
||||||
## Status
|
|
||||||
Accepted
|
|
||||||
|
|
||||||
## Context
|
|
||||||
ADR-0002 established Go 1.24.3 as the minimum required version. Since then:
|
|
||||||
|
|
||||||
- Go 1.25.3 has been released with new features, performance improvements, and security fixes
|
|
||||||
- The project requires access to newer language features and tooling improvements
|
|
||||||
- Dependencies may benefit from Go 1.25.3 optimizations
|
|
||||||
- The development team has Go 1.25.3 available and working
|
|
||||||
|
|
||||||
## Decision
|
|
||||||
Upgrade from **Go 1.24.3** to **Go 1.25.3** as the minimum required version for the platform.
|
|
||||||
|
|
||||||
This decision supersedes [ADR-0002: Go Version](./0002-go-version.md).
|
|
||||||
|
|
||||||
**Rationale:**
|
|
||||||
- Access to latest Go features and performance improvements
|
|
||||||
- Better security with latest patches
|
|
||||||
- Improved tooling support and compiler optimizations
|
|
||||||
- Ensures compatibility with latest ecosystem dependencies
|
|
||||||
- Supports all planned features (modules, plugins, generics) with enhanced capabilities
|
|
||||||
|
|
||||||
## Consequences
|
|
||||||
|
|
||||||
### Positive
|
|
||||||
- Access to latest Go features and performance improvements
|
|
||||||
- Better security with latest patches
|
|
||||||
- Modern tooling support with improved compiler optimizations
|
|
||||||
- Better compatibility with latest dependency versions
|
|
||||||
- Enhanced performance characteristics
|
|
||||||
|
|
||||||
### Negative
|
|
||||||
- Requires developers to upgrade to Go 1.25.3+
|
|
||||||
- CI/CD must be updated to use Go 1.25.3
|
|
||||||
- May require dependency updates for compatibility
|
|
||||||
- Slightly higher barrier for developers still on older versions
|
|
||||||
|
|
||||||
### Mitigations
|
|
||||||
1. **Clear Documentation**: Update all documentation to specify Go 1.25.3 requirement
|
|
||||||
2. **CI/CD Updates**: Ensure CI/CD pipelines use Go 1.25.3
|
|
||||||
3. **Version Check**: Add version validation in build scripts if needed
|
|
||||||
4. **Developer Communication**: Notify team of version requirement
|
|
||||||
|
|
||||||
## Implementation Notes
|
|
||||||
- Update `go.mod`: `go 1.25.3`
|
|
||||||
- Update `.github/workflows/ci.yml` to use `actions/setup-go@v5` with version `1.25.3`
|
|
||||||
- Update ADR-0002 to mark as Superseded
|
|
||||||
- Update README.md and other documentation to reflect Go 1.25.3 requirement
|
|
||||||
- Run `go mod tidy` to ensure dependency compatibility
|
|
||||||
|
|
||||||
## References
|
|
||||||
- [ADR-0002: Go Version](./0002-go-version.md) - Superseded by this ADR
|
|
||||||
- [Go 1.25 Release Notes](https://go.dev/doc/go1.25)
|
|
||||||
|
|
||||||
@@ -5,7 +5,6 @@ This directory contains Architecture Decision Records (ADRs) for the Go Platform
|
|||||||
## What are ADRs?
|
## What are ADRs?
|
||||||
|
|
||||||
ADRs document important architectural decisions made during the project. They help:
|
ADRs document important architectural decisions made during the project. They help:
|
||||||
|
|
||||||
- Track why decisions were made
|
- Track why decisions were made
|
||||||
- Understand the context and constraints
|
- Understand the context and constraints
|
||||||
- Review decisions when requirements change
|
- Review decisions when requirements change
|
||||||
@@ -14,7 +13,6 @@ ADRs document important architectural decisions made during the project. They he
|
|||||||
## ADR Format
|
## ADR Format
|
||||||
|
|
||||||
Each ADR follows this structure:
|
Each ADR follows this structure:
|
||||||
|
|
||||||
- **Status**: Proposed | Accepted | Rejected | Superseded
|
- **Status**: Proposed | Accepted | Rejected | Superseded
|
||||||
- **Context**: The situation that led to the decision
|
- **Context**: The situation that led to the decision
|
||||||
- **Decision**: What was decided
|
- **Decision**: What was decided
|
||||||
@@ -25,8 +23,7 @@ Each ADR follows this structure:
|
|||||||
### Epic 0: Project Setup & Foundation
|
### Epic 0: Project Setup & Foundation
|
||||||
|
|
||||||
- [ADR-0001: Go Module Path](./0001-go-module-path.md) - Module path: `git.dcentral.systems/toolz/goplt`
|
- [ADR-0001: Go Module Path](./0001-go-module-path.md) - Module path: `git.dcentral.systems/toolz/goplt`
|
||||||
- [ADR-0002: Go Version](./0002-go-version.md) - Go 1.24.3 (Superseded by ADR-0034)
|
- [ADR-0002: Go Version](./0002-go-version.md) - Go 1.24.3
|
||||||
- [ADR-0034: Go Version Upgrade to 1.25.3](./0034-go-version-upgrade.md) - Go 1.25.3
|
|
||||||
- [ADR-0003: Dependency Injection Framework](./0003-dependency-injection-framework.md) - uber-go/fx
|
- [ADR-0003: Dependency Injection Framework](./0003-dependency-injection-framework.md) - uber-go/fx
|
||||||
- [ADR-0004: Configuration Management](./0004-configuration-management.md) - spf13/viper + cobra
|
- [ADR-0004: Configuration Management](./0004-configuration-management.md) - spf13/viper + cobra
|
||||||
- [ADR-0005: Logging Framework](./0005-logging-framework.md) - go.uber.org/zap
|
- [ADR-0005: Logging Framework](./0005-logging-framework.md) - go.uber.org/zap
|
||||||
@@ -74,11 +71,9 @@ Each ADR follows this structure:
|
|||||||
|
|
||||||
### Architecture & Scaling
|
### Architecture & Scaling
|
||||||
|
|
||||||
- [ADR-0029: Microservices Architecture](./0029-microservices-architecture.md) - micromicroservices architecture from day one
|
- [ADR-0029: Microservices Architecture](./0029-microservices-architecture.md) - Microservices architecture from day one
|
||||||
- [ADR-0030: Service Communication Strategy](./0030-service-communication-strategy.md) - Service client abstraction and communication patterns with API Gateway
|
- [ADR-0030: Service Communication Strategy](./0030-service-communication-strategy.md) - Service client abstraction and communication patterns
|
||||||
- [ADR-0031: Service Repository Structure](./0031-service-repository-structure.md) - Monorepo with service directories
|
- [ADR-0031: Service Repository Structure](./0031-service-repository-structure.md) - Monorepo with service directories
|
||||||
- [ADR-0032: API Gateway Strategy](./0032-api-gateway-strategy.md) - API Gateway as core infrastructure component
|
|
||||||
- [ADR-0033: Service Discovery Implementation](./0033-service-discovery-implementation.md) - Consul-based service discovery (primary), Kubernetes as alternative
|
|
||||||
|
|
||||||
## Adding New ADRs
|
## Adding New ADRs
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
# Module Architecture
|
# Module Architecture
|
||||||
|
|
||||||
This document details the architecture of modules (feature services), how they are structured as independent services, how they interact with core services, and how multiple services work together in the microservices architecture.
|
This document details the architecture of modules, how they are structured, how they interact with the core platform, and how multiple modules work together.
|
||||||
|
|
||||||
## Table of Contents
|
## Table of Contents
|
||||||
|
|
||||||
@@ -52,33 +52,28 @@ graph TD
|
|||||||
style Service fill:#7b68ee,stroke:#5a4fcf,stroke-width:2px,color:#fff
|
style Service fill:#7b68ee,stroke:#5a4fcf,stroke-width:2px,color:#fff
|
||||||
```
|
```
|
||||||
|
|
||||||
### Service Directory Structure
|
### Module Directory Structure
|
||||||
|
|
||||||
Each module is an independent service with its own entry point:
|
|
||||||
|
|
||||||
```
|
```
|
||||||
cmd/blog-service/
|
modules/blog/
|
||||||
└── main.go # Service entry point
|
├── go.mod # Module dependencies
|
||||||
|
├── module.yaml # Module manifest
|
||||||
services/blog/
|
├── pkg/
|
||||||
├── go.mod # Service dependencies
|
│ └── module.go # IModule implementation
|
||||||
├── module.yaml # Service manifest
|
|
||||||
├── api/
|
|
||||||
│ └── blog.proto # gRPC service definition
|
|
||||||
├── internal/
|
├── internal/
|
||||||
│ ├── api/
|
│ ├── api/
|
||||||
│ │ └── handler.go # gRPC handlers
|
│ │ └── handler.go # HTTP handlers
|
||||||
│ ├── domain/
|
│ ├── domain/
|
||||||
│ │ ├── post.go # Domain entities
|
│ │ ├── post.go # Domain entities
|
||||||
│ │ └── post_repo.go # Repository interface
|
│ │ └── post_repo.go # Repository interface
|
||||||
│ ├── service/
|
│ ├── service/
|
||||||
│ │ └── post_service.go # Business logic
|
│ │ └── post_service.go # Business logic
|
||||||
│ └── database/
|
│ └── ent/
|
||||||
│ └── client.go # Database connection
|
│ ├── schema/
|
||||||
└── ent/
|
│ │ └── post.go # Ent schema
|
||||||
├── schema/
|
│ └── migrate/ # Migrations
|
||||||
│ └── post.go # Ent schema
|
└── tests/
|
||||||
└── migrate/ # Migrations
|
└── integration_test.go
|
||||||
```
|
```
|
||||||
|
|
||||||
## Module Interface
|
## Module Interface
|
||||||
@@ -267,9 +262,9 @@ graph LR
|
|||||||
style Step1 fill:#50c878,stroke:#2e7d4e,stroke-width:2px,color:#fff
|
style Step1 fill:#50c878,stroke:#2e7d4e,stroke-width:2px,color:#fff
|
||||||
```
|
```
|
||||||
|
|
||||||
## Service Communication
|
## Module Communication
|
||||||
|
|
||||||
Modules are implemented as independent services that communicate through service client interfaces. All inter-service communication uses gRPC (primary) or HTTP (fallback) via service clients. Services discover each other through the service registry (Consul).
|
Modules (services) communicate through service client interfaces. All inter-service communication uses gRPC (primary) or HTTP (fallback).
|
||||||
|
|
||||||
### Communication Patterns
|
### Communication Patterns
|
||||||
|
|
||||||
@@ -306,27 +301,14 @@ graph TB
|
|||||||
BlogService -->|gRPC| AuthClient
|
BlogService -->|gRPC| AuthClient
|
||||||
BlogService -->|gRPC| IdentityClient
|
BlogService -->|gRPC| IdentityClient
|
||||||
BlogService -->|gRPC| AuthzClient
|
BlogService -->|gRPC| AuthzClient
|
||||||
BlogService -->|gRPC| AuditClient
|
|
||||||
BlogService -->|Publish| EventBus
|
BlogService -->|Publish| EventBus
|
||||||
EventBus -->|Subscribe| AnalyticsService
|
EventBus -->|Subscribe| AnalyticsService
|
||||||
|
|
||||||
AuthClient -->|Discover| Registry
|
|
||||||
IdentityClient -->|Discover| Registry
|
|
||||||
AuthzClient -->|Discover| Registry
|
|
||||||
AuditClient -->|Discover| Registry
|
|
||||||
|
|
||||||
Registry --> AuthService
|
|
||||||
Registry --> IdentityService
|
|
||||||
Registry --> AuthzService
|
|
||||||
Registry --> AuditService
|
|
||||||
|
|
||||||
AuthClient --> AuthService
|
AuthClient --> AuthService
|
||||||
IdentityClient --> IdentityService
|
IdentityClient --> IdentityService
|
||||||
AuthzClient --> AuthzService
|
AuthzClient --> IdentityService
|
||||||
AuditClient --> AuditService
|
|
||||||
|
|
||||||
style EventBus fill:#4a90e2,stroke:#2e5c8a,stroke-width:3px,color:#fff
|
style EventBus fill:#4a90e2,stroke:#2e5c8a,stroke-width:3px,color:#fff
|
||||||
style Registry fill:#50c878,stroke:#2e7d4e,stroke-width:3px,color:#fff
|
|
||||||
style BlogService fill:#7b68ee,stroke:#5a4fcf,stroke-width:2px,color:#fff
|
style BlogService fill:#7b68ee,stroke:#5a4fcf,stroke-width:2px,color:#fff
|
||||||
style AnalyticsService fill:#7b68ee,stroke:#5a4fcf,stroke-width:2px,color:#fff
|
style AnalyticsService fill:#7b68ee,stroke:#5a4fcf,stroke-width:2px,color:#fff
|
||||||
style ServiceClients fill:#50c878,stroke:#2e7d4e,stroke-width:2px,color:#fff
|
style ServiceClients fill:#50c878,stroke:#2e7d4e,stroke-width:2px,color:#fff
|
||||||
|
|||||||
@@ -13,37 +13,20 @@ This document provides a comprehensive overview of the Go Platform architecture,
|
|||||||
|
|
||||||
## High-Level Architecture
|
## High-Level Architecture
|
||||||
|
|
||||||
The Go Platform follows a **microservices architecture** where each service is independently deployable from day one:
|
The Go Platform follows a **microservices architecture** where each module is an independent service:
|
||||||
|
- **Core Services**: Authentication, Identity, Authorization, Audit, etc.
|
||||||
|
- **Feature Services**: Blog, Billing, Analytics, etc. (modules)
|
||||||
|
- **Infrastructure Services**: Cache, Event Bus, Scheduler, etc.
|
||||||
|
|
||||||
- **Core Kernel**: Infrastructure only (config, logger, DI, health, metrics, observability) - no business logic
|
All services communicate via gRPC (primary) or HTTP (fallback), with service discovery via a service registry. Services share infrastructure (PostgreSQL, Redis, Kafka) but are independently deployable and scalable.
|
||||||
- **Core Services**: Auth Service, Identity Service, Authz Service, Audit Service - separate microservices
|
|
||||||
- **API Gateway**: Single entry point for all external traffic, handles routing and authentication
|
|
||||||
- **Feature Services**: Blog, Billing, Analytics, etc. - independent services
|
|
||||||
- **Infrastructure Adapters**: Cache, Event Bus, Scheduler, etc. - shared infrastructure
|
|
||||||
|
|
||||||
All services communicate via gRPC (primary) or HTTP (fallback) through service client interfaces, with service discovery via a service registry. Each service has its own database connection pool and schema. Services share infrastructure (PostgreSQL instance, Redis, Kafka) but are independently deployable and scalable.
|
|
||||||
|
|
||||||
```mermaid
|
```mermaid
|
||||||
graph TB
|
graph TB
|
||||||
subgraph "API Gateway"
|
subgraph "Go Platform"
|
||||||
Gateway[API Gateway<br/>:8080]
|
Core[Core Kernel]
|
||||||
end
|
Module1[Module 1<br/>Blog]
|
||||||
|
Module2[Module 2<br/>Billing]
|
||||||
subgraph "Core Services"
|
Module3[Module N<br/>Custom]
|
||||||
AuthSvc[Auth Service<br/>:8081]
|
|
||||||
IdentitySvc[Identity Service<br/>:8082]
|
|
||||||
AuthzSvc[Authz Service<br/>:8083]
|
|
||||||
AuditSvc[Audit Service<br/>:8084]
|
|
||||||
end
|
|
||||||
|
|
||||||
subgraph "Feature Services"
|
|
||||||
BlogSvc[Blog Service<br/>:8091]
|
|
||||||
BillingSvc[Billing Service<br/>:8092]
|
|
||||||
AnalyticsSvc[Analytics Service<br/>:8093]
|
|
||||||
end
|
|
||||||
|
|
||||||
subgraph "Core Kernel"
|
|
||||||
Kernel[Core Kernel<br/>Infrastructure Only]
|
|
||||||
end
|
end
|
||||||
|
|
||||||
subgraph "Infrastructure"
|
subgraph "Infrastructure"
|
||||||
@@ -51,7 +34,6 @@ graph TB
|
|||||||
Cache[(Redis)]
|
Cache[(Redis)]
|
||||||
Queue[Kafka/Event Bus]
|
Queue[Kafka/Event Bus]
|
||||||
Storage[S3/Blob Storage]
|
Storage[S3/Blob Storage]
|
||||||
Registry[Service Registry]
|
|
||||||
end
|
end
|
||||||
|
|
||||||
subgraph "External Services"
|
subgraph "External Services"
|
||||||
@@ -60,57 +42,34 @@ graph TB
|
|||||||
Sentry[Sentry]
|
Sentry[Sentry]
|
||||||
end
|
end
|
||||||
|
|
||||||
Gateway --> AuthSvc
|
Core --> DB
|
||||||
Gateway --> IdentitySvc
|
Core --> Cache
|
||||||
Gateway --> AuthzSvc
|
Core --> Queue
|
||||||
Gateway --> BlogSvc
|
Core --> Storage
|
||||||
Gateway --> BillingSvc
|
Core --> OIDC
|
||||||
|
Core --> Email
|
||||||
|
Core --> Sentry
|
||||||
|
|
||||||
AuthSvc --> IdentitySvc
|
Module1 --> Core
|
||||||
AuthSvc --> Registry
|
Module2 --> Core
|
||||||
AuthzSvc --> IdentitySvc
|
Module3 --> Core
|
||||||
AuthzSvc --> Cache
|
|
||||||
AuthzSvc --> AuditSvc
|
|
||||||
BlogSvc --> AuthzSvc
|
|
||||||
BlogSvc --> IdentitySvc
|
|
||||||
BlogSvc --> Registry
|
|
||||||
|
|
||||||
AuthSvc --> DB
|
Module1 --> DB
|
||||||
IdentitySvc --> DB
|
Module2 --> DB
|
||||||
AuthzSvc --> DB
|
Module3 --> DB
|
||||||
AuditSvc --> DB
|
|
||||||
BlogSvc --> DB
|
|
||||||
BillingSvc --> DB
|
|
||||||
|
|
||||||
AuthSvc --> Cache
|
Module1 --> Queue
|
||||||
AuthzSvc --> Cache
|
Module2 --> Queue
|
||||||
BlogSvc --> Cache
|
|
||||||
BillingSvc --> Cache
|
|
||||||
|
|
||||||
BlogSvc --> Queue
|
style Core fill:#4a90e2,stroke:#2e5c8a,stroke-width:3px,color:#fff
|
||||||
BillingSvc --> Queue
|
style Module1 fill:#7b68ee,stroke:#5a4fcf,stroke-width:2px,color:#fff
|
||||||
AnalyticsSvc --> Queue
|
style Module2 fill:#7b68ee,stroke:#5a4fcf,stroke-width:2px,color:#fff
|
||||||
|
style Module3 fill:#7b68ee,stroke:#5a4fcf,stroke-width:2px,color:#fff
|
||||||
Kernel --> DB
|
|
||||||
Kernel --> Cache
|
|
||||||
Kernel --> Queue
|
|
||||||
Kernel --> Registry
|
|
||||||
|
|
||||||
AuthSvc --> OIDC
|
|
||||||
IdentitySvc --> Email
|
|
||||||
AuditSvc --> Sentry
|
|
||||||
|
|
||||||
style Gateway fill:#4a90e2,stroke:#2e5c8a,stroke-width:3px,color:#fff
|
|
||||||
style Kernel fill:#50c878,stroke:#2e7d4e,stroke-width:2px,color:#fff
|
|
||||||
style AuthSvc fill:#ff6b6b,stroke:#c92a2a,stroke-width:2px,color:#fff
|
|
||||||
style IdentitySvc fill:#ff6b6b,stroke:#c92a2a,stroke-width:2px,color:#fff
|
|
||||||
style BlogSvc fill:#7b68ee,stroke:#5a4fcf,stroke-width:2px,color:#fff
|
|
||||||
style BillingSvc fill:#7b68ee,stroke:#5a4fcf,stroke-width:2px,color:#fff
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## Layered Architecture
|
## Layered Architecture
|
||||||
|
|
||||||
The platform follows a **hexagonal architecture** with clear separation of concerns across layers.
|
The platform follows a **clean/hexagonal architecture** with clear separation of concerns across layers.
|
||||||
|
|
||||||
```mermaid
|
```mermaid
|
||||||
graph TD
|
graph TD
|
||||||
@@ -140,13 +99,12 @@ graph TD
|
|||||||
Jobs[Scheduler/Jobs]
|
Jobs[Scheduler/Jobs]
|
||||||
end
|
end
|
||||||
|
|
||||||
subgraph "Core Kernel (Infrastructure Only)"
|
subgraph "Core Kernel"
|
||||||
DI[DI Container]
|
DI[DI Container]
|
||||||
Config[Config Manager]
|
Config[Config Manager]
|
||||||
Logger[Logger]
|
Logger[Logger]
|
||||||
Metrics[Metrics]
|
Metrics[Metrics]
|
||||||
Health[Health Checks]
|
Health[Health Checks]
|
||||||
Tracer[OpenTelemetry Tracer]
|
|
||||||
end
|
end
|
||||||
|
|
||||||
HTTP --> AuthMiddleware
|
HTTP --> AuthMiddleware
|
||||||
@@ -286,7 +244,7 @@ This diagram shows how core components interact with each other and with modules
|
|||||||
|
|
||||||
```mermaid
|
```mermaid
|
||||||
graph TB
|
graph TB
|
||||||
subgraph "Core Kernel Components (Infrastructure)"
|
subgraph "Core Kernel Components"
|
||||||
ConfigMgr[Config Manager]
|
ConfigMgr[Config Manager]
|
||||||
LoggerService[Logger Service]
|
LoggerService[Logger Service]
|
||||||
DI[DI Container]
|
DI[DI Container]
|
||||||
@@ -294,20 +252,15 @@ graph TB
|
|||||||
HealthRegistry[Health Registry]
|
HealthRegistry[Health Registry]
|
||||||
MetricsRegistry[Metrics Registry]
|
MetricsRegistry[Metrics Registry]
|
||||||
ErrorBus[Error Bus]
|
ErrorBus[Error Bus]
|
||||||
Tracer[OpenTelemetry Tracer]
|
EventBus[Event Bus]
|
||||||
ServiceRegistry[Service Registry]
|
|
||||||
end
|
end
|
||||||
|
|
||||||
subgraph "Core Services (Separate Microservices)"
|
subgraph "Security Components"
|
||||||
AuthService[Auth Service<br/>:8081]
|
AuthService[Auth Service]
|
||||||
IdentityService[Identity Service<br/>:8082]
|
AuthzService[Authorization Service]
|
||||||
AuthzService[Authz Service<br/>:8083]
|
TokenProvider[Token Provider]
|
||||||
AuditService[Audit Service<br/>:8084]
|
PermissionResolver[Permission Resolver]
|
||||||
end
|
AuditService[Audit Service]
|
||||||
|
|
||||||
subgraph "Infrastructure Adapters"
|
|
||||||
EventBus[Event Bus<br/>Kafka]
|
|
||||||
CacheService[Cache Service<br/>Redis]
|
|
||||||
end
|
end
|
||||||
|
|
||||||
subgraph "Infrastructure Components"
|
subgraph "Infrastructure Components"
|
||||||
@@ -317,10 +270,6 @@ graph TB
|
|||||||
Notifier[Notifier]
|
Notifier[Notifier]
|
||||||
end
|
end
|
||||||
|
|
||||||
subgraph "External Services"
|
|
||||||
Sentry[Sentry<br/>Error Reporting]
|
|
||||||
end
|
|
||||||
|
|
||||||
subgraph "Module Components"
|
subgraph "Module Components"
|
||||||
ModuleRoutes[Module Routes]
|
ModuleRoutes[Module Routes]
|
||||||
ModuleServices[Module Services]
|
ModuleServices[Module Services]
|
||||||
@@ -333,35 +282,34 @@ graph TB
|
|||||||
DI --> HealthRegistry
|
DI --> HealthRegistry
|
||||||
DI --> MetricsRegistry
|
DI --> MetricsRegistry
|
||||||
DI --> ErrorBus
|
DI --> ErrorBus
|
||||||
DI --> Tracer
|
DI --> EventBus
|
||||||
DI --> ServiceRegistry
|
DI --> AuthService
|
||||||
|
DI --> AuthzService
|
||||||
|
DI --> DBClient
|
||||||
|
DI --> CacheClient
|
||||||
|
DI --> Scheduler
|
||||||
|
DI --> Notifier
|
||||||
|
|
||||||
ModuleServices -->|gRPC| AuthService
|
AuthService --> TokenProvider
|
||||||
ModuleServices -->|gRPC| IdentityService
|
AuthzService --> PermissionResolver
|
||||||
ModuleServices -->|gRPC| AuthzService
|
AuthzService --> AuditService
|
||||||
ModuleServices -->|gRPC| AuditService
|
|
||||||
ModuleServices --> EventBus
|
|
||||||
ModuleServices --> CacheService
|
|
||||||
|
|
||||||
ModuleServices --> DBClient
|
ModuleServices --> DBClient
|
||||||
|
ModuleServices --> CacheClient
|
||||||
|
ModuleServices --> EventBus
|
||||||
|
ModuleServices --> AuthzService
|
||||||
|
|
||||||
ModuleRepos --> DBClient
|
ModuleRepos --> DBClient
|
||||||
|
ModuleRoutes --> AuthzService
|
||||||
|
|
||||||
AuthService --> DBClient
|
Scheduler --> CacheClient
|
||||||
IdentityService --> DBClient
|
Notifier --> EventBus
|
||||||
AuthzService --> DBClient
|
|
||||||
AuditService --> DBClient
|
|
||||||
|
|
||||||
AuthService --> ServiceRegistry
|
|
||||||
IdentityService --> ServiceRegistry
|
|
||||||
AuthzService --> ServiceRegistry
|
|
||||||
AuditService --> ServiceRegistry
|
|
||||||
|
|
||||||
ErrorBus --> LoggerService
|
ErrorBus --> LoggerService
|
||||||
ErrorBus --> Sentry
|
ErrorBus --> Sentry
|
||||||
|
|
||||||
style DI fill:#4a90e2,stroke:#2e5c8a,stroke-width:3px,color:#fff
|
style DI fill:#4a90e2,stroke:#2e5c8a,stroke-width:3px,color:#fff
|
||||||
style AuthService fill:#ff6b6b,stroke:#c92a2a,stroke-width:2px,color:#fff
|
style AuthService fill:#50c878,stroke:#2e7d4e,stroke-width:2px,color:#fff
|
||||||
style IdentityService fill:#ff6b6b,stroke:#c92a2a,stroke-width:2px,color:#fff
|
|
||||||
style ModuleServices fill:#7b68ee,stroke:#5a4fcf,stroke-width:2px,color:#fff
|
style ModuleServices fill:#7b68ee,stroke:#5a4fcf,stroke-width:2px,color:#fff
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -372,28 +320,34 @@ graph TB
|
|||||||
```mermaid
|
```mermaid
|
||||||
sequenceDiagram
|
sequenceDiagram
|
||||||
participant Client
|
participant Client
|
||||||
participant Gateway[API Gateway]
|
participant Router
|
||||||
participant AuthSvc[Auth Service]
|
participant AuthMW[Auth Middleware]
|
||||||
participant AuthzSvc[Authz Service]
|
participant AuthzMW[Authz Middleware]
|
||||||
participant Service[Feature Service]
|
participant RateLimit[Rate Limiter]
|
||||||
participant IdentitySvc[Identity Service]
|
participant Handler
|
||||||
participant AuditSvc[Audit Service]
|
participant Service
|
||||||
participant Repo
|
participant Repo
|
||||||
participant DB
|
participant DB
|
||||||
participant Cache
|
participant Cache
|
||||||
participant EventBus
|
participant EventBus
|
||||||
|
participant Audit
|
||||||
|
|
||||||
Client->>Gateway: HTTP Request
|
Client->>Router: HTTP Request
|
||||||
Gateway->>Gateway: Rate limiting
|
Router->>AuthMW: Extract JWT
|
||||||
Gateway->>AuthSvc: Validate JWT token (gRPC)
|
AuthMW->>AuthMW: Validate token
|
||||||
AuthSvc->>AuthSvc: Verify token
|
AuthMW->>Router: Add user to context
|
||||||
AuthSvc-->>Gateway: Token valid + user info
|
|
||||||
|
|
||||||
Gateway->>AuthzSvc: Check permissions (gRPC)
|
Router->>AuthzMW: Check permissions
|
||||||
AuthzSvc->>AuthzSvc: Resolve permissions
|
AuthzMW->>AuthzMW: Resolve permissions
|
||||||
AuthzSvc-->>Gateway: Authorized
|
AuthzMW->>Router: Authorized
|
||||||
|
|
||||||
Gateway->>Service: Route to service (gRPC/HTTP)
|
Router->>RateLimit: Check rate limits
|
||||||
|
RateLimit->>Cache: Get rate limit state
|
||||||
|
Cache-->>RateLimit: Rate limit status
|
||||||
|
RateLimit->>Router: Within limits
|
||||||
|
|
||||||
|
Router->>Handler: Process request
|
||||||
|
Handler->>Service: Business logic
|
||||||
Service->>Cache: Check cache
|
Service->>Cache: Check cache
|
||||||
Cache-->>Service: Cache miss
|
Cache-->>Service: Cache miss
|
||||||
|
|
||||||
@@ -403,14 +357,12 @@ sequenceDiagram
|
|||||||
Repo-->>Service: Domain entity
|
Repo-->>Service: Domain entity
|
||||||
Service->>Cache: Store in cache
|
Service->>Cache: Store in cache
|
||||||
|
|
||||||
Service->>IdentitySvc: Get user info (gRPC, if needed)
|
|
||||||
IdentitySvc-->>Service: User data
|
|
||||||
|
|
||||||
Service->>EventBus: Publish event
|
Service->>EventBus: Publish event
|
||||||
Service->>AuditSvc: Record action (gRPC)
|
Service->>Audit: Record action
|
||||||
|
|
||||||
Service-->>Gateway: Response data
|
Service-->>Handler: Response data
|
||||||
Gateway-->>Client: JSON response
|
Handler-->>Router: HTTP response
|
||||||
|
Router-->>Client: JSON response
|
||||||
```
|
```
|
||||||
|
|
||||||
### Module Event Flow
|
### Module Event Flow
|
||||||
@@ -456,56 +408,21 @@ graph TB
|
|||||||
end
|
end
|
||||||
|
|
||||||
subgraph "Local Services"
|
subgraph "Local Services"
|
||||||
Gateway[API Gateway<br/>:8080]
|
App[Platform App<br/>:8080]
|
||||||
AuthSvc[Auth Service<br/>:8081]
|
|
||||||
IdentitySvc[Identity Service<br/>:8082]
|
|
||||||
AuthzSvc[Authz Service<br/>:8083]
|
|
||||||
AuditSvc[Audit Service<br/>:8084]
|
|
||||||
BlogSvc[Blog Service<br/>:8091]
|
|
||||||
DB[(PostgreSQL<br/>:5432)]
|
DB[(PostgreSQL<br/>:5432)]
|
||||||
Redis[(Redis<br/>:6379)]
|
Redis[(Redis<br/>:6379)]
|
||||||
Kafka[Kafka<br/>:9092]
|
Kafka[Kafka<br/>:9092]
|
||||||
Consul[Consul<br/>:8500]
|
|
||||||
end
|
end
|
||||||
|
|
||||||
IDE --> Go
|
IDE --> Go
|
||||||
Go --> Gateway
|
Go --> App
|
||||||
Go --> AuthSvc
|
App --> DB
|
||||||
Go --> IdentitySvc
|
App --> Redis
|
||||||
Go --> AuthzSvc
|
App --> Kafka
|
||||||
Go --> AuditSvc
|
|
||||||
Go --> BlogSvc
|
|
||||||
|
|
||||||
Gateway --> AuthSvc
|
|
||||||
Gateway --> IdentitySvc
|
|
||||||
Gateway --> BlogSvc
|
|
||||||
|
|
||||||
AuthSvc --> DB
|
|
||||||
IdentitySvc --> DB
|
|
||||||
AuthzSvc --> DB
|
|
||||||
AuditSvc --> DB
|
|
||||||
BlogSvc --> DB
|
|
||||||
|
|
||||||
AuthSvc --> Redis
|
|
||||||
AuthzSvc --> Redis
|
|
||||||
BlogSvc --> Redis
|
|
||||||
|
|
||||||
BlogSvc --> Kafka
|
|
||||||
|
|
||||||
AuthSvc --> Consul
|
|
||||||
IdentitySvc --> Consul
|
|
||||||
AuthzSvc --> Consul
|
|
||||||
AuditSvc --> Consul
|
|
||||||
BlogSvc --> Consul
|
|
||||||
|
|
||||||
Docker --> DB
|
Docker --> DB
|
||||||
Docker --> Redis
|
Docker --> Redis
|
||||||
Docker --> Kafka
|
Docker --> Kafka
|
||||||
Docker --> Consul
|
|
||||||
|
|
||||||
style Gateway fill:#4a90e2,stroke:#2e5c8a,stroke-width:2px,color:#fff
|
|
||||||
style AuthSvc fill:#ff6b6b,stroke:#c92a2a,stroke-width:2px,color:#fff
|
|
||||||
style IdentitySvc fill:#ff6b6b,stroke:#c92a2a,stroke-width:2px,color:#fff
|
|
||||||
```
|
```
|
||||||
|
|
||||||
### Production Deployment
|
### Production Deployment
|
||||||
@@ -516,15 +433,10 @@ graph TB
|
|||||||
LB[Load Balancer<br/>HTTPS]
|
LB[Load Balancer<br/>HTTPS]
|
||||||
end
|
end
|
||||||
|
|
||||||
subgraph "Service Instances"
|
subgraph "Platform Instances"
|
||||||
Gateway1[API Gateway 1]
|
App1[Platform Instance 1]
|
||||||
Gateway2[API Gateway 2]
|
App2[Platform Instance 2]
|
||||||
AuthSvc1[Auth Service 1]
|
App3[Platform Instance N]
|
||||||
AuthSvc2[Auth Service 2]
|
|
||||||
IdentitySvc1[Identity Service 1]
|
|
||||||
IdentitySvc2[Identity Service 2]
|
|
||||||
BlogSvc1[Blog Service 1]
|
|
||||||
BlogSvc2[Blog Service 2]
|
|
||||||
end
|
end
|
||||||
|
|
||||||
subgraph "Database Cluster"
|
subgraph "Database Cluster"
|
||||||
@@ -555,87 +467,53 @@ graph TB
|
|||||||
S3[S3 Storage]
|
S3[S3 Storage]
|
||||||
end
|
end
|
||||||
|
|
||||||
LB --> Gateway1
|
LB --> App1
|
||||||
LB --> Gateway2
|
LB --> App2
|
||||||
|
LB --> App3
|
||||||
|
|
||||||
Gateway1 --> AuthSvc1
|
App1 --> Primary
|
||||||
Gateway1 --> AuthSvc2
|
App2 --> Primary
|
||||||
Gateway1 --> IdentitySvc1
|
App3 --> Primary
|
||||||
Gateway1 --> IdentitySvc2
|
App1 --> Replica
|
||||||
Gateway1 --> BlogSvc1
|
App2 --> Replica
|
||||||
Gateway1 --> BlogSvc2
|
App3 --> Replica
|
||||||
Gateway2 --> AuthSvc1
|
|
||||||
Gateway2 --> AuthSvc2
|
|
||||||
Gateway2 --> IdentitySvc1
|
|
||||||
Gateway2 --> IdentitySvc2
|
|
||||||
Gateway2 --> BlogSvc1
|
|
||||||
Gateway2 --> BlogSvc2
|
|
||||||
|
|
||||||
AuthSvc1 --> Primary
|
App1 --> Redis1
|
||||||
AuthSvc2 --> Primary
|
App2 --> Redis1
|
||||||
IdentitySvc1 --> Primary
|
App3 --> Redis1
|
||||||
IdentitySvc2 --> Primary
|
|
||||||
BlogSvc1 --> Primary
|
|
||||||
BlogSvc2 --> Primary
|
|
||||||
|
|
||||||
AuthSvc1 --> Replica
|
App1 --> Kafka1
|
||||||
AuthSvc2 --> Replica
|
App2 --> Kafka2
|
||||||
IdentitySvc1 --> Replica
|
App3 --> Kafka3
|
||||||
IdentitySvc2 --> Replica
|
|
||||||
BlogSvc1 --> Replica
|
|
||||||
BlogSvc2 --> Replica
|
|
||||||
|
|
||||||
AuthSvc1 --> Redis1
|
App1 --> Prometheus
|
||||||
AuthSvc2 --> Redis1
|
App2 --> Prometheus
|
||||||
IdentitySvc1 --> Redis1
|
App3 --> Prometheus
|
||||||
IdentitySvc2 --> Redis1
|
|
||||||
BlogSvc1 --> Redis1
|
|
||||||
BlogSvc2 --> Redis1
|
|
||||||
|
|
||||||
BlogSvc1 --> Kafka1
|
|
||||||
BlogSvc2 --> Kafka2
|
|
||||||
|
|
||||||
AuthSvc1 --> Prometheus
|
|
||||||
AuthSvc2 --> Prometheus
|
|
||||||
IdentitySvc1 --> Prometheus
|
|
||||||
IdentitySvc2 --> Prometheus
|
|
||||||
BlogSvc1 --> Prometheus
|
|
||||||
BlogSvc2 --> Prometheus
|
|
||||||
|
|
||||||
Prometheus --> Grafana
|
Prometheus --> Grafana
|
||||||
AuthSvc1 --> Jaeger
|
App1 --> Jaeger
|
||||||
AuthSvc2 --> Jaeger
|
App2 --> Jaeger
|
||||||
IdentitySvc1 --> Jaeger
|
App3 --> Jaeger
|
||||||
IdentitySvc2 --> Jaeger
|
App1 --> Loki
|
||||||
BlogSvc1 --> Jaeger
|
App2 --> Loki
|
||||||
BlogSvc2 --> Jaeger
|
App3 --> Loki
|
||||||
AuthSvc1 --> Loki
|
|
||||||
AuthSvc2 --> Loki
|
|
||||||
IdentitySvc1 --> Loki
|
|
||||||
IdentitySvc2 --> Loki
|
|
||||||
BlogSvc1 --> Loki
|
|
||||||
BlogSvc2 --> Loki
|
|
||||||
|
|
||||||
AuthSvc1 --> Sentry
|
App1 --> Sentry
|
||||||
AuthSvc2 --> Sentry
|
App2 --> Sentry
|
||||||
IdentitySvc1 --> Sentry
|
App3 --> Sentry
|
||||||
IdentitySvc2 --> Sentry
|
|
||||||
BlogSvc1 --> Sentry
|
|
||||||
BlogSvc2 --> Sentry
|
|
||||||
|
|
||||||
BlogSvc1 --> S3
|
App1 --> S3
|
||||||
BlogSvc2 --> S3
|
App2 --> S3
|
||||||
|
App3 --> S3
|
||||||
|
|
||||||
style LB fill:#4a90e2,stroke:#2e5c8a,stroke-width:3px,color:#fff
|
style LB fill:#4a90e2,stroke:#2e5c8a,stroke-width:3px,color:#fff
|
||||||
style Gateway1 fill:#4a90e2,stroke:#2e5c8a,stroke-width:2px,color:#fff
|
|
||||||
style Gateway2 fill:#4a90e2,stroke:#2e5c8a,stroke-width:2px,color:#fff
|
|
||||||
style Primary fill:#50c878,stroke:#2e7d4e,stroke-width:2px,color:#fff
|
style Primary fill:#50c878,stroke:#2e7d4e,stroke-width:2px,color:#fff
|
||||||
style Redis1 fill:#ff6b6b,stroke:#c92a2a,stroke-width:2px,color:#fff
|
style Redis1 fill:#ff6b6b,stroke:#c92a2a,stroke-width:2px,color:#fff
|
||||||
```
|
```
|
||||||
|
|
||||||
## Core Kernel Components
|
## Core Kernel Components
|
||||||
|
|
||||||
The core kernel provides **infrastructure only** - no business logic. It is the foundation that all services depend on. Business logic resides in separate services (Auth, Identity, Authz, Audit).
|
The core kernel provides the foundation for all modules. Each component has specific responsibilities:
|
||||||
|
|
||||||
### Component Responsibilities
|
### Component Responsibilities
|
||||||
|
|
||||||
@@ -658,10 +536,10 @@ mindmap
|
|||||||
Metrics
|
Metrics
|
||||||
Tracing
|
Tracing
|
||||||
Health checks
|
Health checks
|
||||||
Service Discovery
|
Security
|
||||||
Service registry
|
Authentication
|
||||||
Service registration
|
Authorization
|
||||||
Health checking
|
Audit logging
|
||||||
Module System
|
Module System
|
||||||
Module discovery
|
Module discovery
|
||||||
Module loading
|
Module loading
|
||||||
@@ -677,19 +555,8 @@ graph TB
|
|||||||
subgraph "Core Kernel Interfaces"
|
subgraph "Core Kernel Interfaces"
|
||||||
IConfig[ConfigProvider]
|
IConfig[ConfigProvider]
|
||||||
ILogger[Logger]
|
ILogger[Logger]
|
||||||
ITracer[Tracer]
|
IAuth[Authenticator]
|
||||||
IMetrics[Metrics]
|
IAuthz[Authorizer]
|
||||||
IHealth[Health]
|
|
||||||
end
|
|
||||||
|
|
||||||
subgraph "Service Client Interfaces"
|
|
||||||
IAuthClient[AuthServiceClient]
|
|
||||||
IIdentityClient[IdentityServiceClient]
|
|
||||||
IAuthzClient[AuthzServiceClient]
|
|
||||||
IAuditClient[AuditServiceClient]
|
|
||||||
end
|
|
||||||
|
|
||||||
subgraph "Infrastructure Interfaces"
|
|
||||||
IEventBus[EventBus]
|
IEventBus[EventBus]
|
||||||
ICache[Cache]
|
ICache[Cache]
|
||||||
IBlobStore[BlobStore]
|
IBlobStore[BlobStore]
|
||||||
@@ -697,29 +564,23 @@ graph TB
|
|||||||
INotifier[Notifier]
|
INotifier[Notifier]
|
||||||
end
|
end
|
||||||
|
|
||||||
subgraph "Feature Service Implementation"
|
subgraph "Module Implementation"
|
||||||
Module[Feature Service]
|
Module[Feature Module]
|
||||||
ModuleServices[Service Layer]
|
ModuleServices[Module Services]
|
||||||
ModuleRoutes[HTTP/gRPC Routes]
|
ModuleRoutes[Module Routes]
|
||||||
end
|
end
|
||||||
|
|
||||||
Module --> IConfig
|
Module --> IConfig
|
||||||
Module --> ILogger
|
Module --> ILogger
|
||||||
Module --> ITracer
|
ModuleServices --> IAuth
|
||||||
Module --> IMetrics
|
ModuleServices --> IAuthz
|
||||||
Module --> IHealth
|
|
||||||
|
|
||||||
ModuleServices -->|gRPC| IAuthClient
|
|
||||||
ModuleServices -->|gRPC| IIdentityClient
|
|
||||||
ModuleServices -->|gRPC| IAuthzClient
|
|
||||||
ModuleServices -->|gRPC| IAuditClient
|
|
||||||
ModuleServices --> IEventBus
|
ModuleServices --> IEventBus
|
||||||
ModuleServices --> ICache
|
ModuleServices --> ICache
|
||||||
ModuleServices --> IBlobStore
|
ModuleServices --> IBlobStore
|
||||||
ModuleServices --> IScheduler
|
ModuleServices --> IScheduler
|
||||||
ModuleServices --> INotifier
|
ModuleServices --> INotifier
|
||||||
|
|
||||||
ModuleRoutes -->|gRPC| IAuthzClient
|
ModuleRoutes --> IAuthz
|
||||||
|
|
||||||
style IConfig fill:#4a90e2,stroke:#2e5c8a,stroke-width:2px,color:#fff
|
style IConfig fill:#4a90e2,stroke:#2e5c8a,stroke-width:2px,color:#fff
|
||||||
style Module fill:#7b68ee,stroke:#5a4fcf,stroke-width:2px,color:#fff
|
style Module fill:#7b68ee,stroke:#5a4fcf,stroke-width:2px,color:#fff
|
||||||
@@ -876,12 +737,10 @@ graph TB
|
|||||||
- Team autonomy
|
- Team autonomy
|
||||||
|
|
||||||
#### Development Mode
|
#### Development Mode
|
||||||
- For local development, services can run in the same repository/monorepo
|
- For local development, multiple services can run in the same process
|
||||||
- Services still communicate via gRPC/HTTP through service clients (no direct in-process calls)
|
- Services still communicate via gRPC/HTTP (no direct calls)
|
||||||
- Each service has its own process and entry point
|
- Docker Compose for easy local setup
|
||||||
- Docker Compose for easy local setup with all services
|
|
||||||
- Maintains microservices architecture even in development
|
- Maintains microservices architecture even in development
|
||||||
- Services can be started individually for debugging
|
|
||||||
|
|
||||||
## Next Steps
|
## Next Steps
|
||||||
|
|
||||||
|
|||||||
@@ -32,8 +32,10 @@ graph TD
|
|||||||
Scheduler[Scheduler]
|
Scheduler[Scheduler]
|
||||||
end
|
end
|
||||||
|
|
||||||
subgraph "Service Registry"
|
subgraph "Security Layer"
|
||||||
Registry[Service Registry<br/>Consul]
|
Auth[Auth Service]
|
||||||
|
Authz[Authz Service]
|
||||||
|
Audit[Audit Service]
|
||||||
end
|
end
|
||||||
|
|
||||||
subgraph "Observability Layer"
|
subgraph "Observability Layer"
|
||||||
@@ -50,10 +52,17 @@ graph TD
|
|||||||
DI --> Cache
|
DI --> Cache
|
||||||
DI --> EventBus
|
DI --> EventBus
|
||||||
DI --> Scheduler
|
DI --> Scheduler
|
||||||
|
DI --> Auth
|
||||||
|
DI --> Authz
|
||||||
|
DI --> Audit
|
||||||
DI --> Metrics
|
DI --> Metrics
|
||||||
DI --> Health
|
DI --> Health
|
||||||
DI --> Tracer
|
DI --> Tracer
|
||||||
DI --> Registry
|
|
||||||
|
Auth --> DB
|
||||||
|
Authz --> DB
|
||||||
|
Authz --> Cache
|
||||||
|
Audit --> DB
|
||||||
|
|
||||||
DB --> Tracer
|
DB --> Tracer
|
||||||
Cache --> Tracer
|
Cache --> Tracer
|
||||||
@@ -61,11 +70,12 @@ graph TD
|
|||||||
|
|
||||||
style Config fill:#4a90e2,stroke:#2e5c8a,stroke-width:3px,color:#fff
|
style Config fill:#4a90e2,stroke:#2e5c8a,stroke-width:3px,color:#fff
|
||||||
style DI fill:#50c878,stroke:#2e7d4e,stroke-width:3px,color:#fff
|
style DI fill:#50c878,stroke:#2e7d4e,stroke-width:3px,color:#fff
|
||||||
|
style Auth fill:#ff6b6b,stroke:#c92a2a,stroke-width:2px,color:#fff
|
||||||
```
|
```
|
||||||
|
|
||||||
## Service to Service Integration
|
## Module to Core Integration
|
||||||
|
|
||||||
Feature services integrate with core services through service client interfaces. All communication uses gRPC (primary) or HTTP (fallback). Services discover each other via the service registry (Consul).
|
Modules (services) integrate with core services through service client interfaces. All communication uses gRPC or HTTP.
|
||||||
|
|
||||||
```mermaid
|
```mermaid
|
||||||
graph LR
|
graph LR
|
||||||
@@ -76,14 +86,10 @@ graph LR
|
|||||||
end
|
end
|
||||||
|
|
||||||
subgraph "Service Clients"
|
subgraph "Service Clients"
|
||||||
AuthClient[Auth Service Client<br/>gRPC]
|
AuthClient[Auth Service Client]
|
||||||
AuthzClient[Authz Service Client<br/>gRPC]
|
AuthzClient[Authz Service Client]
|
||||||
IdentityClient[Identity Service Client<br/>gRPC]
|
IdentityClient[Identity Service Client]
|
||||||
AuditClient[Audit Service Client<br/>gRPC]
|
AuditClient[Audit Service Client]
|
||||||
end
|
|
||||||
|
|
||||||
subgraph "Service Registry"
|
|
||||||
Registry[Consul<br/>Service Discovery]
|
|
||||||
end
|
end
|
||||||
|
|
||||||
subgraph "Core Services"
|
subgraph "Core Services"
|
||||||
@@ -111,16 +117,6 @@ graph LR
|
|||||||
ModuleService --> EventBusService
|
ModuleService --> EventBusService
|
||||||
ModuleService --> CacheService
|
ModuleService --> CacheService
|
||||||
|
|
||||||
AuthClient -->|Discover| Registry
|
|
||||||
AuthzClient -->|Discover| Registry
|
|
||||||
IdentityClient -->|Discover| Registry
|
|
||||||
AuditClient -->|Discover| Registry
|
|
||||||
|
|
||||||
Registry --> AuthService
|
|
||||||
Registry --> AuthzService
|
|
||||||
Registry --> IdentityService
|
|
||||||
Registry --> AuditService
|
|
||||||
|
|
||||||
AuthClient --> AuthService
|
AuthClient --> AuthService
|
||||||
AuthzClient --> AuthzService
|
AuthzClient --> AuthzService
|
||||||
IdentityClient --> IdentityService
|
IdentityClient --> IdentityService
|
||||||
@@ -131,13 +127,9 @@ graph LR
|
|||||||
EventBusService --> QueueClient
|
EventBusService --> QueueClient
|
||||||
|
|
||||||
style ModuleService fill:#7b68ee,stroke:#5a4fcf,stroke-width:2px,color:#fff
|
style ModuleService fill:#7b68ee,stroke:#5a4fcf,stroke-width:2px,color:#fff
|
||||||
style Registry fill:#50c878,stroke:#2e7d4e,stroke-width:3px,color:#fff
|
style AuthService fill:#4a90e2,stroke:#2e5c8a,stroke-width:2px,color:#fff
|
||||||
style AuthService fill:#ff6b6b,stroke:#c92a2a,stroke-width:2px,color:#fff
|
|
||||||
style DBClient fill:#50c878,stroke:#2e7d4e,stroke-width:2px,color:#fff
|
style DBClient fill:#50c878,stroke:#2e7d4e,stroke-width:2px,color:#fff
|
||||||
style AuthClient fill:#50c878,stroke:#2e7d4e,stroke-width:2px,color:#fff
|
style ServiceClients fill:#50c878,stroke:#2e7d4e,stroke-width:2px,color:#fff
|
||||||
style AuthzClient fill:#50c878,stroke:#2e7d4e,stroke-width:2px,color:#fff
|
|
||||||
style IdentityClient fill:#50c878,stroke:#2e7d4e,stroke-width:2px,color:#fff
|
|
||||||
style AuditClient fill:#50c878,stroke:#2e7d4e,stroke-width:2px,color:#fff
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## Service Interaction Patterns
|
## Service Interaction Patterns
|
||||||
@@ -369,6 +361,7 @@ graph TB
|
|||||||
Notifier --> EventBus
|
Notifier --> EventBus
|
||||||
|
|
||||||
ErrorBus --> Logger
|
ErrorBus --> Logger
|
||||||
|
ErrorBus --> Sentry
|
||||||
|
|
||||||
DB --> Tracer
|
DB --> Tracer
|
||||||
Cache --> Tracer
|
Cache --> Tracer
|
||||||
@@ -477,8 +470,7 @@ graph LR
|
|||||||
style BlogService fill:#7b68ee,stroke:#5a4fcf,stroke-width:2px,color:#fff
|
style BlogService fill:#7b68ee,stroke:#5a4fcf,stroke-width:2px,color:#fff
|
||||||
style AnalyticsService fill:#7b68ee,stroke:#5a4fcf,stroke-width:2px,color:#fff
|
style AnalyticsService fill:#7b68ee,stroke:#5a4fcf,stroke-width:2px,color:#fff
|
||||||
style EventBus fill:#4a90e2,stroke:#2e5c8a,stroke-width:3px,color:#fff
|
style EventBus fill:#4a90e2,stroke:#2e5c8a,stroke-width:3px,color:#fff
|
||||||
style AuthzClient fill:#50c878,stroke:#2e7d4e,stroke-width:2px,color:#fff
|
style ServiceClients fill:#50c878,stroke:#2e7d4e,stroke-width:2px,color:#fff
|
||||||
style IdentityClient fill:#50c878,stroke:#2e7d4e,stroke-width:2px,color:#fff
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## Next Steps
|
## Next Steps
|
||||||
|
|||||||
@@ -19,22 +19,18 @@ Data flows through the platform in multiple patterns depending on the type of op
|
|||||||
|
|
||||||
### Standard HTTP Request Flow
|
### Standard HTTP Request Flow
|
||||||
|
|
||||||
Complete data flow from HTTP request through API Gateway to backend service and response.
|
Complete data flow from HTTP request to response.
|
||||||
|
|
||||||
```mermaid
|
```mermaid
|
||||||
graph TD
|
graph TD
|
||||||
Start[HTTP Request] --> Gateway[API Gateway]
|
Start[HTTP Request] --> Auth[Authentication]
|
||||||
Gateway --> RateLimit{Rate Limit Check}
|
Auth -->|Valid| Authz[Authorization]
|
||||||
RateLimit -->|Allowed| Auth[Validate JWT via Auth Service]
|
Auth -->|Invalid| Error1[401 Response]
|
||||||
RateLimit -->|Exceeded| Error0[429 Too Many Requests]
|
|
||||||
|
|
||||||
Auth -->|Valid| Authz[Check Permission via Authz Service]
|
Authz -->|Authorized| Handler[Request Handler]
|
||||||
Auth -->|Invalid| Error1[401 Unauthorized]
|
Authz -->|Unauthorized| Error2[403 Response]
|
||||||
|
|
||||||
Authz -->|Authorized| Route[Route to Backend Service]
|
Handler --> Service[Domain Service]
|
||||||
Authz -->|Unauthorized| Error2[403 Forbidden]
|
|
||||||
|
|
||||||
Route --> Service[Backend Service]
|
|
||||||
Service --> Cache{Cache Check}
|
Service --> Cache{Cache Check}
|
||||||
|
|
||||||
Cache -->|Hit| CacheData[Return Cached Data]
|
Cache -->|Hit| CacheData[Return Cached Data]
|
||||||
@@ -46,19 +42,17 @@ graph TD
|
|||||||
Service --> CacheStore[Update Cache]
|
Service --> CacheStore[Update Cache]
|
||||||
|
|
||||||
Service --> EventBus[Publish Events]
|
Service --> EventBus[Publish Events]
|
||||||
Service --> AuditSvc[Audit Service<br/>gRPC]
|
Service --> Audit[Audit Log]
|
||||||
Service --> Metrics[Update Metrics]
|
Service --> Metrics[Update Metrics]
|
||||||
|
|
||||||
Service --> Gateway
|
Service --> Handler
|
||||||
Gateway --> Response[HTTP Response]
|
Handler --> Response[HTTP Response]
|
||||||
CacheData --> Gateway
|
CacheData --> Response
|
||||||
Error0 --> Response
|
|
||||||
Error1 --> Response
|
Error1 --> Response
|
||||||
Error2 --> Response
|
Error2 --> Response
|
||||||
|
|
||||||
Response --> Client[Client]
|
Response --> Client[Client]
|
||||||
|
|
||||||
style Gateway fill:#4a90e2,stroke:#2e5c8a,stroke-width:3px,color:#fff
|
|
||||||
style Auth fill:#ff6b6b,stroke:#c92a2a,stroke-width:2px,color:#fff
|
style Auth fill:#ff6b6b,stroke:#c92a2a,stroke-width:2px,color:#fff
|
||||||
style Service fill:#50c878,stroke:#2e7d4e,stroke-width:2px,color:#fff
|
style Service fill:#50c878,stroke:#2e7d4e,stroke-width:2px,color:#fff
|
||||||
style Cache fill:#4a90e2,stroke:#2e5c8a,stroke-width:2px,color:#fff
|
style Cache fill:#4a90e2,stroke:#2e5c8a,stroke-width:2px,color:#fff
|
||||||
@@ -66,30 +60,22 @@ graph TD
|
|||||||
|
|
||||||
### Request Data Transformation
|
### Request Data Transformation
|
||||||
|
|
||||||
How request data is transformed as it flows through API Gateway to backend service.
|
How request data is transformed as it flows through the system.
|
||||||
|
|
||||||
```mermaid
|
```mermaid
|
||||||
sequenceDiagram
|
sequenceDiagram
|
||||||
participant Client
|
participant Client
|
||||||
participant Gateway
|
participant Handler
|
||||||
participant BackendService
|
|
||||||
participant Service
|
participant Service
|
||||||
participant Repo
|
participant Repo
|
||||||
participant DB
|
participant DB
|
||||||
|
|
||||||
Client->>Gateway: HTTP Request (JSON)
|
Client->>Handler: HTTP Request (JSON)
|
||||||
Gateway->>Gateway: Rate limiting
|
Handler->>Handler: Parse JSON
|
||||||
Gateway->>Gateway: Validate JWT (via Auth Service)
|
Handler->>Handler: Validate request
|
||||||
Gateway->>Gateway: Check permission (via Authz Service)
|
Handler->>Handler: Convert to DTO
|
||||||
Gateway->>Gateway: Route to service (via service discovery)
|
|
||||||
Gateway->>Gateway: Forward request (gRPC/HTTP)
|
|
||||||
|
|
||||||
Gateway->>BackendService: Request (gRPC/HTTP)
|
Handler->>Service: Business DTO
|
||||||
BackendService->>BackendService: Parse request
|
|
||||||
BackendService->>BackendService: Validate request
|
|
||||||
BackendService->>BackendService: Convert to DTO
|
|
||||||
|
|
||||||
BackendService->>Service: Business DTO
|
|
||||||
Service->>Service: Business logic
|
Service->>Service: Business logic
|
||||||
Service->>Service: Domain entity
|
Service->>Service: Domain entity
|
||||||
|
|
||||||
@@ -103,13 +89,10 @@ sequenceDiagram
|
|||||||
|
|
||||||
Service->>Service: Business logic
|
Service->>Service: Business logic
|
||||||
Service->>Service: Response DTO
|
Service->>Service: Response DTO
|
||||||
Service-->>BackendService: Response DTO
|
Service-->>Handler: Response DTO
|
||||||
|
|
||||||
BackendService->>BackendService: Convert to response format
|
Handler->>Handler: Convert to JSON
|
||||||
BackendService-->>Gateway: Response (gRPC/HTTP)
|
Handler-->>Client: HTTP Response (JSON)
|
||||||
|
|
||||||
Gateway->>Gateway: Transform response (if needed)
|
|
||||||
Gateway-->>Client: HTTP Response (JSON)
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## Event Data Flow
|
## Event Data Flow
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ This document explains how modules integrate with the core platform, focusing on
|
|||||||
|
|
||||||
## Overview
|
## Overview
|
||||||
|
|
||||||
Modules are implemented as independent services that extend the platform's functionality. They integrate with core services through service client interfaces, service discovery (Consul), and a standardized initialization process. Each module operates as an independent service with its own entry point, database connection, and deployment while leveraging core platform capabilities.
|
Modules are independent services that extend the platform's functionality. They integrate with the core platform through well-defined interfaces, service clients, and a standardized initialization process. Each module operates as an independent service while leveraging core platform capabilities.
|
||||||
|
|
||||||
## Key Concepts
|
## Key Concepts
|
||||||
|
|
||||||
@@ -94,7 +94,7 @@ sequenceDiagram
|
|||||||
DB-->>Module: Migrations registered
|
DB-->>Module: Migrations registered
|
||||||
|
|
||||||
Module->>ServiceRegistry: Register service
|
Module->>ServiceRegistry: Register service
|
||||||
ServiceRegistry->>ServiceRegistry: Register with Consul
|
ServiceRegistry->>ServiceRegistry: Register with registry
|
||||||
ServiceRegistry-->>Module: Service registered
|
ServiceRegistry-->>Module: Service registered
|
||||||
|
|
||||||
Module->>Module: OnStart hook (optional)
|
Module->>Module: OnStart hook (optional)
|
||||||
@@ -247,7 +247,11 @@ sequenceDiagram
|
|||||||
participant AuthzService
|
participant AuthzService
|
||||||
|
|
||||||
Module->>ModuleManifest: Define permissions
|
Module->>ModuleManifest: Define permissions
|
||||||
ModuleManifest->>ModuleManifest: permissions: blog.post.create, blog.post.read, blog.post.update, blog.post.delete
|
ModuleManifest->>ModuleManifest: permissions:
|
||||||
|
- blog.post.create
|
||||||
|
- blog.post.read
|
||||||
|
- blog.post.update
|
||||||
|
- blog.post.delete
|
||||||
|
|
||||||
Module->>PermissionGenerator: Generate permission code
|
Module->>PermissionGenerator: Generate permission code
|
||||||
PermissionGenerator->>PermissionGenerator: Parse manifest
|
PermissionGenerator->>PermissionGenerator: Parse manifest
|
||||||
|
|||||||
@@ -25,22 +25,16 @@ Complete flow of user logging in and receiving authentication tokens.
|
|||||||
sequenceDiagram
|
sequenceDiagram
|
||||||
participant User
|
participant User
|
||||||
participant Client
|
participant Client
|
||||||
participant Gateway[API Gateway]
|
|
||||||
participant AuthService
|
participant AuthService
|
||||||
participant IdentityService
|
participant IdentityService
|
||||||
participant DB
|
participant DB
|
||||||
participant TokenProvider
|
participant TokenProvider
|
||||||
participant AuditService
|
participant AuditService
|
||||||
participant Registry[Consul]
|
|
||||||
|
|
||||||
User->>Client: Enter credentials
|
User->>Client: Enter credentials
|
||||||
Client->>Gateway: POST /api/v1/auth/login
|
Client->>AuthService: POST /api/v1/auth/login
|
||||||
Gateway->>Gateway: Rate limiting check
|
|
||||||
Gateway->>AuthService: Login request (gRPC)
|
|
||||||
AuthService->>AuthService: Validate request format
|
AuthService->>AuthService: Validate request format
|
||||||
AuthService->>Registry: Discover Identity Service
|
AuthService->>IdentityService: Verify credentials
|
||||||
Registry-->>AuthService: Identity Service endpoint
|
|
||||||
AuthService->>IdentityService: Verify credentials (gRPC)
|
|
||||||
IdentityService->>DB: Query user by email
|
IdentityService->>DB: Query user by email
|
||||||
DB-->>IdentityService: User data
|
DB-->>IdentityService: User data
|
||||||
IdentityService->>IdentityService: Verify password hash
|
IdentityService->>IdentityService: Verify password hash
|
||||||
@@ -55,14 +49,11 @@ sequenceDiagram
|
|||||||
DB-->>TokenProvider: Token stored
|
DB-->>TokenProvider: Token stored
|
||||||
TokenProvider-->>AuthService: Refresh token
|
TokenProvider-->>AuthService: Refresh token
|
||||||
|
|
||||||
AuthService->>Registry: Discover Audit Service
|
AuthService->>AuditService: Log login
|
||||||
Registry-->>AuthService: Audit Service endpoint
|
|
||||||
AuthService->>AuditService: Log login (gRPC)
|
|
||||||
AuditService->>DB: Store audit log
|
AuditService->>DB: Store audit log
|
||||||
AuditService-->>AuthService: Logged
|
AuditService-->>AuthService: Logged
|
||||||
|
|
||||||
AuthService-->>Gateway: Access + Refresh tokens
|
AuthService-->>Client: Access + Refresh tokens
|
||||||
Gateway-->>Client: Access + Refresh tokens
|
|
||||||
Client-->>User: Authentication successful
|
Client-->>User: Authentication successful
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -72,25 +63,23 @@ How the system checks if a user has permission to perform an action.
|
|||||||
|
|
||||||
```mermaid
|
```mermaid
|
||||||
sequenceDiagram
|
sequenceDiagram
|
||||||
participant Gateway[API Gateway]
|
|
||||||
participant Handler
|
participant Handler
|
||||||
|
participant AuthzMiddleware
|
||||||
participant AuthzService
|
participant AuthzService
|
||||||
participant PermissionResolver
|
participant PermissionResolver
|
||||||
participant Cache
|
participant Cache
|
||||||
participant DB
|
participant DB
|
||||||
participant IdentityService
|
participant IdentityService
|
||||||
participant Registry[Consul]
|
|
||||||
|
|
||||||
Gateway->>Handler: Request with user context
|
Handler->>AuthzMiddleware: Check permission
|
||||||
Handler->>AuthzService: Authorize(user, permission) (gRPC)
|
AuthzMiddleware->>AuthzMiddleware: Extract user from context
|
||||||
AuthzService->>Registry: Discover Identity Service
|
AuthzMiddleware->>AuthzService: Authorize(user, permission)
|
||||||
Registry-->>AuthzService: Identity Service endpoint
|
|
||||||
|
|
||||||
AuthzService->>Cache: Check permission cache
|
AuthzService->>Cache: Check permission cache
|
||||||
Cache-->>AuthzService: Cache miss
|
Cache-->>AuthzService: Cache miss
|
||||||
|
|
||||||
AuthzService->>PermissionResolver: Resolve permissions
|
AuthzService->>PermissionResolver: Resolve permissions
|
||||||
PermissionResolver->>IdentityService: Get user roles (gRPC)
|
PermissionResolver->>IdentityService: Get user roles
|
||||||
IdentityService->>DB: Query user roles
|
IdentityService->>DB: Query user roles
|
||||||
DB-->>IdentityService: User roles
|
DB-->>IdentityService: User roles
|
||||||
IdentityService-->>PermissionResolver: Roles list
|
IdentityService-->>PermissionResolver: Roles list
|
||||||
@@ -102,12 +91,12 @@ sequenceDiagram
|
|||||||
|
|
||||||
AuthzService->>AuthzService: Check permission in list
|
AuthzService->>AuthzService: Check permission in list
|
||||||
AuthzService->>Cache: Store in cache
|
AuthzService->>Cache: Store in cache
|
||||||
AuthzService-->>Handler: Authorized/Unauthorized
|
AuthzService-->>AuthzMiddleware: Authorized/Unauthorized
|
||||||
|
|
||||||
alt Authorized
|
alt Authorized
|
||||||
Handler-->>Gateway: Continue request
|
AuthzMiddleware-->>Handler: Continue
|
||||||
else Unauthorized
|
else Unauthorized
|
||||||
Handler-->>Gateway: 403 Forbidden
|
AuthzMiddleware-->>Handler: 403 Forbidden
|
||||||
end
|
end
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
@@ -6,12 +6,12 @@ This document explains how services work together in the Go Platform's microserv
|
|||||||
|
|
||||||
## Overview
|
## Overview
|
||||||
|
|
||||||
The Go Platform consists of multiple independent services that communicate via service clients (gRPC/HTTP) and share infrastructure components. Services are discovered and registered through a service registry (Consul), enabling dynamic service location and health monitoring.
|
The Go Platform consists of multiple independent services that communicate via service clients (gRPC/HTTP) and share infrastructure components. Services are discovered and registered through a service registry, enabling dynamic service location and health monitoring.
|
||||||
|
|
||||||
## Key Concepts
|
## Key Concepts
|
||||||
|
|
||||||
- **Service**: Independent process providing specific functionality
|
- **Service**: Independent process providing specific functionality
|
||||||
- **Service Registry**: Central registry for service discovery (Consul - primary, Kubernetes as alternative)
|
- **Service Registry**: Central registry for service discovery (Consul, Kubernetes, etcd)
|
||||||
- **Service Client**: Abstraction for inter-service communication
|
- **Service Client**: Abstraction for inter-service communication
|
||||||
- **Service Discovery**: Process of locating services by name
|
- **Service Discovery**: Process of locating services by name
|
||||||
- **Service Health**: Health status of a service (healthy, unhealthy, degraded)
|
- **Service Health**: Health status of a service (healthy, unhealthy, degraded)
|
||||||
@@ -59,7 +59,7 @@ Services automatically register themselves with the service registry on startup
|
|||||||
sequenceDiagram
|
sequenceDiagram
|
||||||
participant Service
|
participant Service
|
||||||
participant ServiceRegistry
|
participant ServiceRegistry
|
||||||
participant Registry[Consul<br/>Service Registry]
|
participant Registry[Consul/K8s]
|
||||||
participant Client
|
participant Client
|
||||||
|
|
||||||
Service->>ServiceRegistry: Register(serviceInfo)
|
Service->>ServiceRegistry: Register(serviceInfo)
|
||||||
@@ -93,7 +93,7 @@ sequenceDiagram
|
|||||||
|
|
||||||
1. **Service Startup**: Service initializes and loads configuration
|
1. **Service Startup**: Service initializes and loads configuration
|
||||||
2. **Service Info Creation**: Create service info with name, version, address, protocol
|
2. **Service Info Creation**: Create service info with name, version, address, protocol
|
||||||
3. **Registry Registration**: Register service with Consul (primary) or Kubernetes service discovery (alternative)
|
3. **Registry Registration**: Register service with Consul/Kubernetes/etc
|
||||||
4. **Health Check Setup**: Start health check endpoint
|
4. **Health Check Setup**: Start health check endpoint
|
||||||
5. **Health Status Updates**: Periodically update health status in registry
|
5. **Health Status Updates**: Periodically update health status in registry
|
||||||
6. **Service Discovery**: Clients query registry for service endpoints
|
6. **Service Discovery**: Clients query registry for service endpoints
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ This document provides a high-level explanation of how the Go Platform behaves e
|
|||||||
|
|
||||||
## Overview
|
## Overview
|
||||||
|
|
||||||
The Go Platform is a microservices-based system where each service is independently deployable from day one. Services communicate via gRPC (primary) or HTTP (fallback) through service clients, share infrastructure components (PostgreSQL instance, Redis, Kafka), and are orchestrated through service discovery and dependency injection. All external traffic enters through the API Gateway.
|
The Go Platform is a microservices-based system where each module operates as an independent service. Services communicate via gRPC (primary) or HTTP (fallback), share infrastructure components (PostgreSQL, Redis, Kafka), and are orchestrated through service discovery and dependency injection.
|
||||||
|
|
||||||
## Key Concepts
|
## Key Concepts
|
||||||
|
|
||||||
@@ -16,11 +16,9 @@ The Go Platform is a microservices-based system where each service is independen
|
|||||||
- **Event Bus**: Asynchronous communication channel for events
|
- **Event Bus**: Asynchronous communication channel for events
|
||||||
- **DI Container**: Dependency injection container managing service lifecycle
|
- **DI Container**: Dependency injection container managing service lifecycle
|
||||||
|
|
||||||
## Service Bootstrap Sequence
|
## Application Bootstrap Sequence
|
||||||
|
|
||||||
Each service (API Gateway, Auth, Identity, Authz, Audit, and feature services) follows a well-defined startup sequence. Services bootstrap independently.
|
The platform follows a well-defined startup sequence that ensures all services are properly initialized and registered.
|
||||||
|
|
||||||
### Individual Service Startup
|
|
||||||
|
|
||||||
```mermaid
|
```mermaid
|
||||||
sequenceDiagram
|
sequenceDiagram
|
||||||
@@ -28,9 +26,9 @@ sequenceDiagram
|
|||||||
participant Config
|
participant Config
|
||||||
participant Logger
|
participant Logger
|
||||||
participant DI
|
participant DI
|
||||||
participant ServiceImpl
|
participant Registry
|
||||||
|
participant ModuleLoader
|
||||||
participant ServiceRegistry
|
participant ServiceRegistry
|
||||||
participant DB
|
|
||||||
participant HTTP
|
participant HTTP
|
||||||
participant gRPC
|
participant gRPC
|
||||||
|
|
||||||
@@ -41,121 +39,63 @@ sequenceDiagram
|
|||||||
Logger-->>Main: Logger ready
|
Logger-->>Main: Logger ready
|
||||||
|
|
||||||
Main->>DI: Create DI container
|
Main->>DI: Create DI container
|
||||||
DI->>DI: Register core kernel services
|
DI->>DI: Register core services
|
||||||
DI-->>Main: DI container ready
|
DI-->>Main: DI container ready
|
||||||
|
|
||||||
Main->>ServiceImpl: Register service implementation
|
Main->>ModuleLoader: Discover modules
|
||||||
ServiceImpl->>DI: Register service dependencies
|
ModuleLoader->>ModuleLoader: Scan module directories
|
||||||
ServiceImpl->>DB: Connect to database
|
ModuleLoader->>ModuleLoader: Load module.yaml files
|
||||||
DB-->>ServiceImpl: Connection ready
|
ModuleLoader-->>Main: Module list
|
||||||
|
|
||||||
Main->>DB: Run migrations
|
Main->>Registry: Register modules
|
||||||
DB-->>Main: Migrations complete
|
Registry->>Registry: Resolve dependencies
|
||||||
|
Registry->>Registry: Order modules
|
||||||
|
Registry-->>Main: Ordered modules
|
||||||
|
|
||||||
|
loop For each module
|
||||||
|
Main->>Module: Initialize module
|
||||||
|
Module->>DI: Register services
|
||||||
|
Module->>Registry: Register routes
|
||||||
|
Module->>Registry: Register migrations
|
||||||
|
end
|
||||||
|
|
||||||
|
Main->>Registry: Run migrations
|
||||||
|
Registry->>Registry: Execute in dependency order
|
||||||
|
|
||||||
Main->>ServiceRegistry: Register service
|
Main->>ServiceRegistry: Register service
|
||||||
ServiceRegistry->>ServiceRegistry: Register with Consul/K8s
|
ServiceRegistry->>ServiceRegistry: Register with Consul/K8s
|
||||||
ServiceRegistry-->>Main: Service registered
|
ServiceRegistry-->>Main: Service registered
|
||||||
|
|
||||||
Main->>gRPC: Start gRPC server
|
Main->>gRPC: Start gRPC server
|
||||||
Main->>HTTP: Start HTTP server (if needed)
|
Main->>HTTP: Start HTTP server
|
||||||
HTTP-->>Main: HTTP server ready
|
HTTP-->>Main: Server ready
|
||||||
gRPC-->>Main: gRPC server ready
|
gRPC-->>Main: Server ready
|
||||||
|
|
||||||
Main->>DI: Start lifecycle
|
Main->>DI: Start lifecycle
|
||||||
DI->>DI: Execute OnStart hooks
|
DI->>DI: Execute OnStart hooks
|
||||||
DI-->>Main: Service started
|
DI-->>Main: All services started
|
||||||
```
|
```
|
||||||
|
|
||||||
### Platform Startup (All Services)
|
### Bootstrap Phases
|
||||||
|
|
||||||
```mermaid
|
|
||||||
sequenceDiagram
|
|
||||||
participant Docker
|
|
||||||
participant Gateway
|
|
||||||
participant AuthSvc
|
|
||||||
participant IdentitySvc
|
|
||||||
participant AuthzSvc
|
|
||||||
participant AuditSvc
|
|
||||||
participant BlogSvc
|
|
||||||
participant Registry
|
|
||||||
participant DB
|
|
||||||
|
|
||||||
Docker->>DB: Start PostgreSQL
|
|
||||||
Docker->>Registry: Start Consul
|
|
||||||
DB-->>Docker: Database ready
|
|
||||||
Registry-->>Docker: Registry ready
|
|
||||||
|
|
||||||
par Service Startup (in parallel)
|
|
||||||
Docker->>Gateway: Start API Gateway
|
|
||||||
Gateway->>Registry: Register
|
|
||||||
Gateway->>Gateway: Start HTTP server
|
|
||||||
Gateway-->>Docker: Gateway ready
|
|
||||||
and
|
|
||||||
Docker->>AuthSvc: Start Auth Service
|
|
||||||
AuthSvc->>DB: Connect
|
|
||||||
AuthSvc->>Registry: Register
|
|
||||||
AuthSvc->>AuthSvc: Start gRPC server
|
|
||||||
AuthSvc-->>Docker: Auth Service ready
|
|
||||||
and
|
|
||||||
Docker->>IdentitySvc: Start Identity Service
|
|
||||||
IdentitySvc->>DB: Connect
|
|
||||||
IdentitySvc->>Registry: Register
|
|
||||||
IdentitySvc->>IdentitySvc: Start gRPC server
|
|
||||||
IdentitySvc-->>Docker: Identity Service ready
|
|
||||||
and
|
|
||||||
Docker->>AuthzSvc: Start Authz Service
|
|
||||||
AuthzSvc->>DB: Connect
|
|
||||||
AuthzSvc->>Registry: Register
|
|
||||||
AuthzSvc->>AuthzSvc: Start gRPC server
|
|
||||||
AuthzSvc-->>Docker: Authz Service ready
|
|
||||||
and
|
|
||||||
Docker->>AuditSvc: Start Audit Service
|
|
||||||
AuditSvc->>DB: Connect
|
|
||||||
AuditSvc->>Registry: Register
|
|
||||||
AuditSvc->>AuditSvc: Start gRPC server
|
|
||||||
AuditSvc-->>Docker: Audit Service ready
|
|
||||||
and
|
|
||||||
Docker->>BlogSvc: Start Blog Service
|
|
||||||
BlogSvc->>DB: Connect
|
|
||||||
BlogSvc->>Registry: Register
|
|
||||||
BlogSvc->>BlogSvc: Start gRPC server
|
|
||||||
BlogSvc-->>Docker: Blog Service ready
|
|
||||||
end
|
|
||||||
|
|
||||||
Docker->>Docker: All services ready
|
|
||||||
```
|
|
||||||
|
|
||||||
### Service Bootstrap Phases (Per Service)
|
|
||||||
|
|
||||||
1. **Configuration Loading**: Load YAML files, environment variables, and secrets
|
1. **Configuration Loading**: Load YAML files, environment variables, and secrets
|
||||||
2. **Foundation Services**: Initialize core kernel (logger, config, DI container)
|
2. **Foundation Services**: Initialize logger, config provider, DI container
|
||||||
3. **Database Connection**: Connect to database with own connection pool
|
3. **Module Discovery**: Scan and load module manifests
|
||||||
4. **Service Implementation**: Register service-specific implementations
|
4. **Dependency Resolution**: Build dependency graph and order modules
|
||||||
5. **Database Migrations**: Run service-specific migrations
|
5. **Module Initialization**: Initialize each module in dependency order
|
||||||
6. **Service Registration**: Register service with service registry
|
6. **Database Migrations**: Run migrations in dependency order
|
||||||
7. **Server Startup**: Start gRPC server (and HTTP if needed)
|
7. **Service Registration**: Register service with service registry
|
||||||
8. **Lifecycle Hooks**: Execute OnStart hooks
|
8. **Server Startup**: Start HTTP and gRPC servers
|
||||||
|
9. **Lifecycle Hooks**: Execute OnStart hooks for all services
|
||||||
### Platform Startup Order
|
|
||||||
|
|
||||||
1. **Infrastructure**: Start PostgreSQL, Redis, Kafka, Consul
|
|
||||||
2. **Core Services**: Start Auth, Identity, Authz, Audit services (can start in parallel)
|
|
||||||
3. **API Gateway**: Start API Gateway (depends on service registry)
|
|
||||||
4. **Feature Services**: Start Blog, Billing, etc. (can start in parallel)
|
|
||||||
5. **Health Checks**: All services report healthy to registry
|
|
||||||
|
|
||||||
## Request Processing Pipeline
|
## Request Processing Pipeline
|
||||||
|
|
||||||
Every HTTP request flows through API Gateway first, then to backend services. The pipeline ensures security, observability, and proper error handling.
|
Every HTTP request flows through a standardized pipeline that ensures security, observability, and proper error handling.
|
||||||
|
|
||||||
```mermaid
|
```mermaid
|
||||||
graph TD
|
graph TD
|
||||||
Start([HTTP Request]) --> Gateway[API Gateway]
|
Start([HTTP Request]) --> Auth[Authentication Middleware]
|
||||||
Gateway --> RateLimit[Rate Limiting]
|
Auth -->|Valid Token| Authz[Authorization Middleware]
|
||||||
RateLimit -->|Allowed| Auth[Validate JWT via Auth Service]
|
|
||||||
RateLimit -->|Exceeded| Error0[429 Too Many Requests]
|
|
||||||
|
|
||||||
Auth -->|Valid Token| Authz[Check Permission via Authz Service]
|
|
||||||
Auth -->|Invalid Token| Error1[401 Unauthorized]
|
Auth -->|Invalid Token| Error1[401 Unauthorized]
|
||||||
|
|
||||||
Authz -->|Authorized| RateLimit[Rate Limiting]
|
Authz -->|Authorized| RateLimit[Rate Limiting]
|
||||||
@@ -387,7 +327,7 @@ Health checks and metrics provide visibility into system health and performance.
|
|||||||
|
|
||||||
```mermaid
|
```mermaid
|
||||||
graph TD
|
graph TD
|
||||||
HealthEndpoint["/healthz"] --> HealthRegistry[Health Registry]
|
HealthEndpoint[/healthz] --> HealthRegistry[Health Registry]
|
||||||
HealthRegistry --> CheckDB[Check Database]
|
HealthRegistry --> CheckDB[Check Database]
|
||||||
HealthRegistry --> CheckCache[Check Cache]
|
HealthRegistry --> CheckCache[Check Cache]
|
||||||
HealthRegistry --> CheckEventBus[Check Event Bus]
|
HealthRegistry --> CheckEventBus[Check Event Bus]
|
||||||
@@ -399,7 +339,7 @@ graph TD
|
|||||||
Aggregate -->|All Healthy| Response200[200 OK]
|
Aggregate -->|All Healthy| Response200[200 OK]
|
||||||
Aggregate -->|Unhealthy| Response503[503 Service Unavailable]
|
Aggregate -->|Unhealthy| Response503[503 Service Unavailable]
|
||||||
|
|
||||||
MetricsEndpoint["/metrics"] --> MetricsRegistry[Metrics Registry]
|
MetricsEndpoint[/metrics] --> MetricsRegistry[Metrics Registry]
|
||||||
MetricsRegistry --> Prometheus[Prometheus Format]
|
MetricsRegistry --> Prometheus[Prometheus Format]
|
||||||
Prometheus --> ResponseMetrics[Metrics Response]
|
Prometheus --> ResponseMetrics[Metrics Response]
|
||||||
|
|
||||||
|
|||||||
@@ -4,25 +4,22 @@ Welcome to the Go Platform documentation! This is a plugin-friendly SaaS/Enterpr
|
|||||||
|
|
||||||
## What is Go Platform?
|
## What is Go Platform?
|
||||||
|
|
||||||
Go Platform is a microservices platform designed to support multiple business domains through independent, deployable services. It provides:
|
Go Platform is a modular, extensible platform designed to support multiple business domains through a plugin architecture. It provides:
|
||||||
|
|
||||||
- **Core Kernel**: Infrastructure only (configuration, logging, DI, health, metrics, observability) - no business logic
|
- **Core Kernel**: Foundation services including authentication, authorization, configuration, logging, and observability
|
||||||
- **Core Services**: Independent microservices (Auth, Identity, Authz, Audit) with their own entry points and databases
|
- **Module Framework**: Plugin system for extending functionality
|
||||||
- **API Gateway**: Single entry point for all external traffic, handles routing, authentication, and rate limiting
|
|
||||||
- **Service Discovery**: Consul-based service registry for dynamic service discovery
|
|
||||||
- **Module Framework**: Feature services (Blog, Billing, etc.) as independent services
|
|
||||||
- **Infrastructure Adapters**: Support for databases, caching, event buses, and job scheduling
|
- **Infrastructure Adapters**: Support for databases, caching, event buses, and job scheduling
|
||||||
- **Security-by-Design**: Built-in JWT authentication, RBAC/ABAC authorization, and audit logging
|
- **Security-by-Design**: Built-in JWT authentication, RBAC/ABAC authorization, and audit logging
|
||||||
- **Observability**: OpenTelemetry integration for distributed tracing, metrics, and logging across services
|
- **Observability**: OpenTelemetry integration for tracing, metrics, and logging
|
||||||
|
|
||||||
## Documentation Structure
|
## Documentation Structure
|
||||||
|
|
||||||
### Overview
|
### 📋 Overview
|
||||||
- **[Requirements](requirements.md)**: High-level architectural principles and requirements
|
- **[Requirements](requirements.md)**: High-level architectural principles and requirements
|
||||||
- **[Implementation Plan](plan.md)**: Epic-based implementation plan with timelines
|
- **[Implementation Plan](plan.md)**: Epic-based implementation plan with timelines
|
||||||
- **[Playbook](playbook.md)**: Detailed implementation guide and best practices
|
- **[Playbook](playbook.md)**: Detailed implementation guide and best practices
|
||||||
|
|
||||||
### Architecture
|
### 🏛️ Architecture
|
||||||
- **[Architecture Overview](architecture/architecture.md)**: System architecture with diagrams
|
- **[Architecture Overview](architecture/architecture.md)**: System architecture with diagrams
|
||||||
- **[Module Architecture](architecture/architecture-modules.md)**: Module system design and integration
|
- **[Module Architecture](architecture/architecture-modules.md)**: Module system design and integration
|
||||||
- **[Module Requirements](architecture/module-requirements.md)**: Detailed requirements for each module
|
- **[Module Requirements](architecture/module-requirements.md)**: Detailed requirements for each module
|
||||||
@@ -34,9 +31,8 @@ Go Platform is a microservices platform designed to support multiple business do
|
|||||||
- **[Operational Scenarios](architecture/operational-scenarios.md)**: Common operational flows and use cases
|
- **[Operational Scenarios](architecture/operational-scenarios.md)**: Common operational flows and use cases
|
||||||
- **[Data Flow Patterns](architecture/data-flow-patterns.md)**: How data flows through the system
|
- **[Data Flow Patterns](architecture/data-flow-patterns.md)**: How data flows through the system
|
||||||
|
|
||||||
### Architecture Decision Records (ADRs)
|
### 🏗️ Architecture Decision Records (ADRs)
|
||||||
All architectural decisions are documented in [ADR records](adr/README.md), organized by implementation epic:
|
All architectural decisions are documented in [ADR records](adr/README.md), organized by implementation epic:
|
||||||
|
|
||||||
- **Epic 0**: Project Setup & Foundation
|
- **Epic 0**: Project Setup & Foundation
|
||||||
- **Epic 1**: Core Kernel & Infrastructure
|
- **Epic 1**: Core Kernel & Infrastructure
|
||||||
- **Epic 2**: Authentication & Authorization
|
- **Epic 2**: Authentication & Authorization
|
||||||
@@ -45,9 +41,8 @@ All architectural decisions are documented in [ADR records](adr/README.md), orga
|
|||||||
- **Epic 6**: Observability & Production Readiness
|
- **Epic 6**: Observability & Production Readiness
|
||||||
- **Epic 7**: Testing, Documentation & CI/CD
|
- **Epic 7**: Testing, Documentation & CI/CD
|
||||||
|
|
||||||
### Implementation Tasks
|
### 📝 Implementation Tasks
|
||||||
Detailed task definitions for each epic are available in the [Stories section](stories/README.md):
|
Detailed task definitions for each epic are available in the [Stories section](stories/README.md):
|
||||||
|
|
||||||
- **[Epic 0: Project Setup & Foundation](stories/epic0/README.md)** - [Implementation Summary](stories/epic0/SUMMARY.md)
|
- **[Epic 0: Project Setup & Foundation](stories/epic0/README.md)** - [Implementation Summary](stories/epic0/SUMMARY.md)
|
||||||
- **[Epic 1: Core Kernel & Infrastructure](stories/epic1/README.md)** - [Implementation Summary](stories/epic1/SUMMARY.md)
|
- **[Epic 1: Core Kernel & Infrastructure](stories/epic1/README.md)** - [Implementation Summary](stories/epic1/SUMMARY.md)
|
||||||
- Epic 2: Authentication & Authorization
|
- Epic 2: Authentication & Authorization
|
||||||
@@ -68,17 +63,11 @@ Detailed task definitions for each epic are available in the [Stories section](s
|
|||||||
|
|
||||||
## Key Principles
|
## Key Principles
|
||||||
|
|
||||||
- **microMicroservices Architecture**: Each service is independently deployable from day one
|
- **Clean/Hexagonal Architecture**: Clear separation between core and plugins
|
||||||
- Core Kernel: Infrastructure only (config, logger, DI, health, metrics)
|
- **Microservices Architecture**: Each module is an independent service from day one
|
||||||
- Core Services: Auth, Identity, Authz, Audit as separate services
|
- **Plugin-First Design**: Extensible architecture supporting static and dynamic modules
|
||||||
- Feature Services: Blog, Billing, etc. as independent services
|
|
||||||
- **API Gateway**: Single entry point for all external traffic
|
|
||||||
- **Service Discovery**: Consul-based service registry for dynamic service location
|
|
||||||
- **Service Clients**: All inter-service communication via gRPC/HTTP through service clients
|
|
||||||
- **Database Isolation**: Each service has its own database connection pool and schema
|
|
||||||
- **Hexagonal Architecture**: Clear separation between interfaces and implementations
|
|
||||||
- **Security-by-Design**: Built-in authentication, authorization, and audit capabilities
|
- **Security-by-Design**: Built-in authentication, authorization, and audit capabilities
|
||||||
- **Observability**: Comprehensive distributed tracing, metrics, and logging across services
|
- **Observability**: Comprehensive logging, metrics, and tracing
|
||||||
- **API-First**: OpenAPI/GraphQL schema generation
|
- **API-First**: OpenAPI/GraphQL schema generation
|
||||||
|
|
||||||
## Contributing
|
## Contributing
|
||||||
|
|||||||
@@ -13,15 +13,11 @@ This plan breaks down the implementation into **8 epics**, each with specific de
|
|||||||
**Total Estimated Timeline:** 8-12 weeks (depending on team size and parallelization)
|
**Total Estimated Timeline:** 8-12 weeks (depending on team size and parallelization)
|
||||||
|
|
||||||
**Key Principles:**
|
**Key Principles:**
|
||||||
- **Hexagonal Architecture** with clear separation between `pkg/` (interfaces) and `internal/` (implementations)
|
- **Clean/Hexagonal Architecture** with clear separation between `pkg/` (interfaces) and `internal/` (implementations)
|
||||||
- **Dependency Injection** using `uber-go/fx` for lifecycle management
|
- **Dependency Injection** using `uber-go/fx` for lifecycle management
|
||||||
- **microMicroservices Architecture** - each service is independently deployable from day one
|
- **Microservices Architecture** - each module is an independent service from day one
|
||||||
- Core Kernel: Infrastructure only (config, logger, DI, health, metrics, observability)
|
|
||||||
- Core Services: Auth, Identity, Authz, Audit as separate microservices
|
|
||||||
- API Gateway: Single entry point for all external traffic (Epic 1)
|
|
||||||
- **Service Client Interfaces** - all inter-service communication via gRPC/HTTP
|
- **Service Client Interfaces** - all inter-service communication via gRPC/HTTP
|
||||||
- **Service Discovery** - all services register and discover via service registry
|
- **Service Discovery** - all services register and discover via service registry
|
||||||
- **Database Isolation** - each service has its own database connection pool and schema
|
|
||||||
- **Plugin-first** architecture supporting both static and dynamic module loading
|
- **Plugin-first** architecture supporting both static and dynamic module loading
|
||||||
- **Security-by-Design** with JWT auth, RBAC/ABAC, and audit logging
|
- **Security-by-Design** with JWT auth, RBAC/ABAC, and audit logging
|
||||||
- **Observability** via OpenTelemetry, Prometheus, and structured logging
|
- **Observability** via OpenTelemetry, Prometheus, and structured logging
|
||||||
@@ -175,71 +171,69 @@ This plan breaks down the implementation into **8 epics**, each with specific de
|
|||||||
## Epic 1: Core Kernel & Infrastructure (Week 2-3)
|
## Epic 1: Core Kernel & Infrastructure (Week 2-3)
|
||||||
|
|
||||||
### Objectives
|
### Objectives
|
||||||
- Extend DI container to support core kernel infrastructure only (no business services)
|
- Extend DI container to support all core services
|
||||||
- Implement API Gateway as core infrastructure component
|
- Implement database layer with Ent ORM
|
||||||
- Create service client interfaces for microservices architecture
|
|
||||||
- Build health monitoring and metrics system
|
- Build health monitoring and metrics system
|
||||||
- Create error handling and error bus
|
- Create error handling and error bus
|
||||||
- Establish HTTP/gRPC server foundation for services
|
- Establish HTTP server with comprehensive middleware stack
|
||||||
- Integrate OpenTelemetry for distributed tracing
|
- Integrate OpenTelemetry for distributed tracing
|
||||||
- Basic service registry implementation
|
- Create service client interfaces for microservices architecture
|
||||||
|
|
||||||
**Note:** This epic focuses on infrastructure only. Business services (Auth, Identity, Authz, Audit) are implemented in Epic 2 as separate microservices.
|
|
||||||
|
|
||||||
### Stories
|
### Stories
|
||||||
|
|
||||||
#### 1.1 Enhanced Dependency Injection Container
|
#### 1.1 Enhanced Dependency Injection Container
|
||||||
**Goal:** Extend the DI container to provide core kernel infrastructure services only (no business logic services).
|
**Goal:** Extend the DI container to provide all core infrastructure services with proper lifecycle management.
|
||||||
|
|
||||||
**Deliverables:**
|
**Deliverables:**
|
||||||
- Extended `internal/di/container.go` with:
|
- Extended `internal/di/container.go` with:
|
||||||
- Registration of core kernel services only
|
- Registration of all core services
|
||||||
- Lifecycle management via FX
|
- Lifecycle management via FX
|
||||||
- Service override support for testing
|
- Service override support for testing
|
||||||
- `internal/di/providers.go` with provider functions:
|
- `internal/di/providers.go` with provider functions:
|
||||||
- `ProvideConfig()` - configuration provider
|
- `ProvideConfig()` - configuration provider
|
||||||
- `ProvideLogger()` - logger service
|
- `ProvideLogger()` - logger service
|
||||||
|
- `ProvideDatabase()` - Ent database client
|
||||||
- `ProvideHealthCheckers()` - health check registry
|
- `ProvideHealthCheckers()` - health check registry
|
||||||
- `ProvideMetrics()` - Prometheus metrics registry
|
- `ProvideMetrics()` - Prometheus metrics registry
|
||||||
- `ProvideErrorBus()` - error bus service
|
- `ProvideErrorBus()` - error bus service
|
||||||
- `ProvideTracer()` - OpenTelemetry tracer
|
- `internal/di/core_module.go` exporting `CoreModule` fx.Option that provides all core services
|
||||||
- `ProvideServiceRegistry()` - service registry interface
|
|
||||||
- `internal/di/core_module.go` exporting `CoreModule` fx.Option that provides all core kernel services
|
|
||||||
|
|
||||||
**Note:** Database, Auth, Identity, Authz, Audit are NOT in core kernel - they are separate services implemented in Epic 2.
|
|
||||||
|
|
||||||
**Acceptance Criteria:**
|
**Acceptance Criteria:**
|
||||||
- All core kernel services are provided via DI container
|
- All core services are provided via DI container
|
||||||
- Services are initialized in correct dependency order
|
- Services are initialized in correct dependency order
|
||||||
- Lifecycle hooks work for all services
|
- Lifecycle hooks work for all services
|
||||||
- Services can be overridden for testing
|
- Services can be overridden for testing
|
||||||
- DI container compiles without errors
|
- DI container compiles without errors
|
||||||
- No business logic services in core kernel
|
|
||||||
|
|
||||||
#### 1.2 Database Client Foundation
|
#### 1.2 Database Layer with Ent ORM
|
||||||
**Goal:** Set up database client foundation for services. Each service will have its own database connection and schema.
|
**Goal:** Set up a complete database layer using Ent ORM with core domain entities, migrations, and connection management.
|
||||||
|
|
||||||
**Deliverables:**
|
**Deliverables:**
|
||||||
- Database client wrapper in `internal/infra/database/client.go`:
|
- Ent schema initialization and core entities:
|
||||||
- `NewEntClient(dsn string, schema string) (*ent.Client, error)` - supports schema isolation
|
- `User` entity: ID, email, password_hash, verified, created_at, updated_at
|
||||||
|
- `Role` entity: ID, name, description, created_at
|
||||||
|
- `Permission` entity: ID, name (format: "module.resource.action")
|
||||||
|
- `AuditLog` entity: ID, actor_id, action, target_id, metadata (JSON), timestamp
|
||||||
|
- Many-to-many relationships: `role_permissions` and `user_roles`
|
||||||
|
- Generated Ent code with proper type safety
|
||||||
|
- Database client in `internal/infra/database/client.go`:
|
||||||
|
- `NewEntClient(dsn string) (*ent.Client, error)`
|
||||||
- Connection pooling configuration (max connections, idle timeout)
|
- Connection pooling configuration (max connections, idle timeout)
|
||||||
- Migration runner wrapper
|
- Migration runner wrapper
|
||||||
- Database health check integration
|
- Database health check integration
|
||||||
- Per-service connection pool management
|
|
||||||
- Database configuration in `config/default.yaml` with:
|
- Database configuration in `config/default.yaml` with:
|
||||||
- Connection string (DSN)
|
- Connection string (DSN)
|
||||||
- Connection pool settings per service
|
- Connection pool settings
|
||||||
- Schema isolation configuration
|
- Migration settings
|
||||||
- Database client factory for creating service-specific clients
|
|
||||||
|
|
||||||
**Note:** Core domain entities (User, Role, Permission, AuditLog) are implemented in Epic 2 as part of their respective services (Identity, Authz, Audit).
|
|
||||||
|
|
||||||
**Acceptance Criteria:**
|
**Acceptance Criteria:**
|
||||||
- Database client connects to PostgreSQL with schema support
|
- Ent schema compiles and generates code successfully
|
||||||
|
- Database client connects to PostgreSQL
|
||||||
|
- Core entities can be created and queried
|
||||||
|
- Migrations run successfully on startup
|
||||||
- Connection pooling is configured correctly
|
- Connection pooling is configured correctly
|
||||||
- Database health check works
|
- Database health check works
|
||||||
- Multiple services can connect to same database instance with different schemas
|
- All entities have proper indexes and relationships
|
||||||
- Each service manages its own connection pool
|
|
||||||
|
|
||||||
#### 1.3 Health Monitoring and Metrics System
|
#### 1.3 Health Monitoring and Metrics System
|
||||||
**Goal:** Implement comprehensive health checks and Prometheus metrics for monitoring platform health and performance.
|
**Goal:** Implement comprehensive health checks and Prometheus metrics for monitoring platform health and performance.
|
||||||
@@ -295,13 +289,13 @@ This plan breaks down the implementation into **8 epics**, each with specific de
|
|||||||
- Error context (request ID, user ID) is preserved
|
- Error context (request ID, user ID) is preserved
|
||||||
- Background error consumer works correctly
|
- Background error consumer works correctly
|
||||||
|
|
||||||
#### 1.5 HTTP/gRPC Server Foundation
|
#### 1.5 HTTP Server Foundation with Middleware Stack
|
||||||
**Goal:** Create HTTP and gRPC server foundation that services can use. Each service will have its own server instance.
|
**Goal:** Create a production-ready HTTP server with comprehensive middleware for security, observability, and error handling.
|
||||||
|
|
||||||
**Deliverables:**
|
**Deliverables:**
|
||||||
- HTTP server foundation in `internal/server/http.go`:
|
- HTTP server in `internal/server/server.go`:
|
||||||
- Gin router initialization helper
|
- Gin router initialization
|
||||||
- Common middleware stack:
|
- Comprehensive middleware stack:
|
||||||
- Request ID generator (unique per request)
|
- Request ID generator (unique per request)
|
||||||
- Structured logging middleware (logs all requests)
|
- Structured logging middleware (logs all requests)
|
||||||
- Panic recovery → error bus
|
- Panic recovery → error bus
|
||||||
@@ -309,54 +303,28 @@ This plan breaks down the implementation into **8 epics**, each with specific de
|
|||||||
- CORS support (configurable)
|
- CORS support (configurable)
|
||||||
- Request timeout handling
|
- Request timeout handling
|
||||||
- Response compression
|
- Response compression
|
||||||
- Server lifecycle management
|
- Core route registration:
|
||||||
- gRPC server foundation in `internal/server/grpc.go`:
|
- `GET /healthz` - liveness probe
|
||||||
- gRPC server initialization
|
- `GET /ready` - readiness probe
|
||||||
- Interceptor support (logging, tracing, metrics)
|
- `GET /metrics` - Prometheus metrics
|
||||||
- Lifecycle management
|
- FX lifecycle integration:
|
||||||
- FX lifecycle integration for both HTTP and gRPC servers
|
- HTTP server starts on `OnStart` hook
|
||||||
|
- Graceful shutdown on `OnStop` hook (drains connections)
|
||||||
**Note:** Services (Auth, Identity, etc.) will use these foundations to create their own server instances in Epic 2.
|
- Port configuration from config
|
||||||
|
- Integration with main application entry point
|
||||||
|
|
||||||
**Acceptance Criteria:**
|
**Acceptance Criteria:**
|
||||||
- HTTP server foundation is reusable by services
|
- HTTP server starts successfully
|
||||||
- gRPC server foundation is reusable by services
|
|
||||||
- All middleware executes in correct order
|
- All middleware executes in correct order
|
||||||
- Request IDs are generated and logged
|
- Request IDs are generated and logged
|
||||||
- Metrics are collected for all requests
|
- Metrics are collected for all requests
|
||||||
|
- Panics are recovered and handled
|
||||||
- Graceful shutdown works correctly
|
- Graceful shutdown works correctly
|
||||||
- Servers are configurable via config system
|
- Server is configurable via config system
|
||||||
|
- CORS is configurable per environment
|
||||||
|
|
||||||
#### 1.6 OpenTelemetry Distributed Tracing
|
#### 1.6 OpenTelemetry Distributed Tracing
|
||||||
**Goal:** Integrate OpenTelemetry for distributed tracing across all services to enable observability in production.
|
**Goal:** Integrate OpenTelemetry for distributed tracing across the platform to enable observability in production.
|
||||||
|
|
||||||
**Deliverables:**
|
|
||||||
- OpenTelemetry setup in `internal/observability/tracer.go`:
|
|
||||||
- TracerProvider initialization
|
|
||||||
- Export to stdout (development mode)
|
|
||||||
- Export to OTLP collector (production mode)
|
|
||||||
- Trace context propagation
|
|
||||||
- HTTP instrumentation middleware:
|
|
||||||
- Automatic span creation for HTTP requests
|
|
||||||
- Trace context propagation via headers
|
|
||||||
- Span attributes (method, path, status code, etc.)
|
|
||||||
- gRPC instrumentation:
|
|
||||||
- gRPC interceptor for automatic span creation
|
|
||||||
- Trace context propagation via gRPC metadata
|
|
||||||
- Database instrumentation:
|
|
||||||
- Ent interceptor for database queries
|
|
||||||
- Query spans with timing and parameters
|
|
||||||
- Integration with logger (include trace ID in logs)
|
|
||||||
|
|
||||||
**Acceptance Criteria:**
|
|
||||||
- HTTP requests create OpenTelemetry spans
|
|
||||||
- gRPC calls create OpenTelemetry spans
|
|
||||||
- Database queries are traced
|
|
||||||
- Trace context propagates across service boundaries
|
|
||||||
- Trace IDs are included in logs
|
|
||||||
- Traces export correctly to configured backend
|
|
||||||
- Tracing works in both development and production modes
|
|
||||||
- Tracing has minimal performance impact
|
|
||||||
|
|
||||||
#### 1.7 Service Client Interfaces
|
#### 1.7 Service Client Interfaces
|
||||||
**Goal:** Create service client interfaces for all core services to enable microservices communication.
|
**Goal:** Create service client interfaces for all core services to enable microservices communication.
|
||||||
@@ -366,6 +334,7 @@ This plan breaks down the implementation into **8 epics**, each with specific de
|
|||||||
- `IdentityServiceClient` - User and identity operations
|
- `IdentityServiceClient` - User and identity operations
|
||||||
- `AuthServiceClient` - Authentication operations
|
- `AuthServiceClient` - Authentication operations
|
||||||
- `AuthzServiceClient` - Authorization operations
|
- `AuthzServiceClient` - Authorization operations
|
||||||
|
- `PermissionServiceClient` - Permission resolution
|
||||||
- `AuditServiceClient` - Audit logging
|
- `AuditServiceClient` - Audit logging
|
||||||
- Service client factory in `internal/services/factory.go`:
|
- Service client factory in `internal/services/factory.go`:
|
||||||
- Create gRPC clients (primary)
|
- Create gRPC clients (primary)
|
||||||
@@ -382,89 +351,69 @@ This plan breaks down the implementation into **8 epics**, each with specific de
|
|||||||
- Configuration supports protocol selection
|
- Configuration supports protocol selection
|
||||||
- All inter-service communication goes through service clients
|
- All inter-service communication goes through service clients
|
||||||
|
|
||||||
#### 1.8 API Gateway Implementation
|
|
||||||
**Goal:** Implement API Gateway as core infrastructure component that routes all external traffic to backend services.
|
|
||||||
|
|
||||||
**Deliverables:**
|
**Deliverables:**
|
||||||
- API Gateway service entry point: `cmd/api-gateway/main.go`
|
- OpenTelemetry setup in `internal/observability/tracer.go`:
|
||||||
- Gateway implementation in `services/gateway/internal/`:
|
- TracerProvider initialization
|
||||||
- Request routing to backend services via service discovery
|
- Export to stdout (development mode)
|
||||||
- JWT token validation via Auth Service client
|
- Export to OTLP collector (production mode)
|
||||||
- Permission checking via Authz Service client (optional, for route-level auth)
|
- Trace context propagation
|
||||||
- Rate limiting middleware (per-user and per-IP)
|
- HTTP instrumentation middleware:
|
||||||
- CORS support
|
- Automatic span creation for HTTP requests
|
||||||
- Request/response transformation
|
- Trace context propagation via headers
|
||||||
- Load balancing across service instances
|
- Span attributes (method, path, status code, etc.)
|
||||||
- Gateway configuration in `config/default.yaml`:
|
- Database instrumentation:
|
||||||
- Route definitions (path → service mapping)
|
- Ent interceptor for database queries
|
||||||
- Rate limiting configuration
|
- Query spans with timing and parameters
|
||||||
- CORS configuration
|
- Integration with logger (include trace ID in logs)
|
||||||
- Integration with service registry for service discovery
|
|
||||||
- Health check endpoint for gateway
|
|
||||||
|
|
||||||
**Acceptance Criteria:**
|
**Acceptance Criteria:**
|
||||||
- API Gateway routes requests to backend services correctly
|
- HTTP requests create OpenTelemetry spans
|
||||||
- JWT validation works via Auth Service
|
- Database queries are traced
|
||||||
- Rate limiting works correctly
|
- Trace context propagates across service boundaries
|
||||||
- CORS is configurable and works
|
- Trace IDs are included in logs
|
||||||
- Service discovery integration works
|
- Traces export correctly to configured backend
|
||||||
- Gateway is independently deployable
|
- Tracing works in both development and production modes
|
||||||
- Gateway has health check endpoint
|
- Tracing has minimal performance impact
|
||||||
- All external traffic goes through gateway
|
|
||||||
|
|
||||||
### Deliverables
|
### Deliverables
|
||||||
- ✅ DI container with core kernel services only
|
- ✅ DI container with all core services
|
||||||
- ✅ Database client foundation (per-service connections)
|
- ✅ Database client with Ent schema
|
||||||
- ✅ Health and metrics endpoints functional
|
- ✅ Health and metrics endpoints functional
|
||||||
- ✅ Error bus captures and logs errors
|
- ✅ Error bus captures and logs errors
|
||||||
- ✅ HTTP/gRPC server foundation for services
|
- ✅ HTTP server with middleware stack
|
||||||
- ✅ Basic observability with OpenTelemetry
|
- ✅ Basic observability with OpenTelemetry
|
||||||
- ✅ Service client interfaces for microservices
|
- ✅ Service client interfaces for microservices
|
||||||
- ✅ API Gateway service (core infrastructure)
|
|
||||||
- ✅ Basic service registry implementation
|
|
||||||
|
|
||||||
### Acceptance Criteria
|
### Acceptance Criteria
|
||||||
- `GET /healthz` returns 200 for all services
|
- `GET /healthz` returns 200
|
||||||
- `GET /ready` checks service health
|
- `GET /ready` checks DB connectivity
|
||||||
- `GET /metrics` exposes Prometheus metrics
|
- `GET /metrics` exposes Prometheus metrics
|
||||||
- Panic recovery logs errors via error bus
|
- Panic recovery logs errors via error bus
|
||||||
- HTTP/gRPC requests are traced with OpenTelemetry
|
- Database migrations run on startup
|
||||||
- API Gateway routes requests to backend services
|
- HTTP requests are traced with OpenTelemetry
|
||||||
- Service client interfaces are defined
|
|
||||||
- No business logic services in Epic 1
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Epic 2: Core Services (Authentication & Authorization) (Week 3-5)
|
## Epic 2: Authentication & Authorization (Week 3-4)
|
||||||
|
|
||||||
### Objectives
|
### Objectives
|
||||||
- Separate Auth, Identity, Authz, and Audit into independent microservices
|
- Implement complete JWT-based authentication system
|
||||||
- Each service has its own entry point, database connection, and gRPC server
|
- Build comprehensive identity management with user lifecycle
|
||||||
- Implement complete JWT-based authentication system (Auth Service)
|
- Create role-based access control (RBAC) system
|
||||||
- Build comprehensive identity management with user lifecycle (Identity Service)
|
- Implement authorization middleware and permission checks
|
||||||
- Create role-based access control (RBAC) system (Authz Service)
|
|
||||||
- Implement audit logging system (Audit Service)
|
|
||||||
- All services communicate via service clients (gRPC/HTTP)
|
|
||||||
- All services register with service registry
|
|
||||||
|
|
||||||
**Note:** This epic transforms the monolithic core into separate, independently deployable services.
|
|
||||||
- Add comprehensive audit logging for security compliance
|
- Add comprehensive audit logging for security compliance
|
||||||
- Provide database seeding for initial setup
|
- Provide database seeding for initial setup
|
||||||
|
|
||||||
### Stories
|
### Stories
|
||||||
|
|
||||||
#### 2.1 Auth Service - JWT Authentication
|
#### 2.1 JWT Authentication System
|
||||||
**Goal:** Implement Auth Service as an independent microservice with complete JWT-based authentication system, access tokens, refresh tokens, and secure token management.
|
**Goal:** Implement a complete JWT-based authentication system with access tokens, refresh tokens, and secure token management.
|
||||||
|
|
||||||
**Deliverables:**
|
**Deliverables:**
|
||||||
- Auth Service entry point: `cmd/auth-service/main.go`
|
- Authentication interfaces in `pkg/auth/auth.go`:
|
||||||
- Service implementation in `services/auth/internal/`:
|
- `Authenticator` interface for token generation and verification
|
||||||
- gRPC server for Auth Service
|
- `TokenClaims` struct with user ID, roles, tenant ID, expiration
|
||||||
- HTTP endpoints (optional, for compatibility)
|
- JWT implementation in `internal/auth/jwt_auth.go`:
|
||||||
- Authentication interfaces in `pkg/services/auth.go`:
|
|
||||||
- `AuthServiceClient` interface for authentication operations
|
|
||||||
- Service client implementation (gRPC/HTTP)
|
|
||||||
- JWT implementation in `services/auth/internal/jwt_auth.go`:
|
|
||||||
- Generate short-lived access tokens (15 minutes)
|
- Generate short-lived access tokens (15 minutes)
|
||||||
- Generate long-lived refresh tokens (7 days)
|
- Generate long-lived refresh tokens (7 days)
|
||||||
- Token signature verification
|
- Token signature verification
|
||||||
@@ -475,41 +424,29 @@ This plan breaks down the implementation into **8 epics**, each with specific de
|
|||||||
- Verify token validity
|
- Verify token validity
|
||||||
- Inject authenticated user into request context
|
- Inject authenticated user into request context
|
||||||
- Helper function: `auth.FromContext(ctx) *User`
|
- Helper function: `auth.FromContext(ctx) *User`
|
||||||
- gRPC service definition: `services/auth/api/auth.proto`
|
- Authentication endpoints:
|
||||||
- Authentication endpoints (gRPC):
|
- `POST /api/v1/auth/login` - Authenticate user and return tokens
|
||||||
- `Login(email, password)` - Authenticate user and return tokens
|
- `POST /api/v1/auth/refresh` - Refresh access token using refresh token
|
||||||
- `RefreshToken(refresh_token)` - Refresh access token using refresh token
|
- Password validation against stored hashes
|
||||||
- `ValidateToken(token)` - Validate JWT token (used by API Gateway)
|
- Integration with DI container and HTTP server
|
||||||
- Password validation against stored hashes (via Identity Service)
|
|
||||||
- Database connection: Own connection pool and schema (`auth_schema`)
|
|
||||||
- Service registration: Register with service registry
|
|
||||||
- Integration with Identity Service: Use `IdentityServiceClient` for user lookup
|
|
||||||
|
|
||||||
**Acceptance Criteria:**
|
**Acceptance Criteria:**
|
||||||
- Auth Service is independently deployable
|
- Users can login and receive access and refresh tokens
|
||||||
- Service has own entry point (`cmd/auth-service/`)
|
|
||||||
- gRPC server starts and serves authentication requests
|
|
||||||
- Users can login via gRPC and receive access and refresh tokens
|
|
||||||
- Access tokens expire after configured duration
|
- Access tokens expire after configured duration
|
||||||
- Refresh tokens can be used to obtain new access tokens
|
- Refresh tokens can be used to obtain new access tokens
|
||||||
- Token validation works (used by API Gateway)
|
|
||||||
- Invalid tokens are rejected with appropriate errors
|
- Invalid tokens are rejected with appropriate errors
|
||||||
- Service registers with service registry
|
- Authenticated user is available in request context
|
||||||
- Service uses Identity Service client for user lookup
|
- Login attempts are logged
|
||||||
- Service has own database connection and schema
|
- Token secrets are configurable
|
||||||
|
|
||||||
#### 2.2 Identity Service - User Management
|
#### 2.2 Identity Management System
|
||||||
**Goal:** Implement Identity Service as an independent microservice with complete user identity management, registration, email verification, password management, and user CRUD operations.
|
**Goal:** Build a complete user identity management system with registration, email verification, password management, and user CRUD operations.
|
||||||
|
|
||||||
**Deliverables:**
|
**Deliverables:**
|
||||||
- Identity Service entry point: `cmd/identity-service/main.go`
|
- Identity interfaces in `pkg/identity/identity.go`:
|
||||||
- Service implementation in `services/identity/internal/`:
|
- `UserRepository` interface for user data access
|
||||||
- gRPC server for Identity Service
|
- `UserService` interface for user business logic
|
||||||
- HTTP endpoints (optional, for compatibility)
|
- User repository implementation in `internal/identity/user_repo.go`:
|
||||||
- Identity interfaces in `pkg/services/identity.go`:
|
|
||||||
- `IdentityServiceClient` interface for user operations
|
|
||||||
- Service client implementation (gRPC/HTTP)
|
|
||||||
- User repository implementation in `services/identity/internal/user_repo.go`:
|
|
||||||
- CRUD operations using Ent
|
- CRUD operations using Ent
|
||||||
- Password hashing (bcrypt or argon2)
|
- Password hashing (bcrypt or argon2)
|
||||||
- Email uniqueness validation
|
- Email uniqueness validation
|
||||||
@@ -520,34 +457,24 @@ This plan breaks down the implementation into **8 epics**, each with specific de
|
|||||||
- Password reset flow (token-based, time-limited)
|
- Password reset flow (token-based, time-limited)
|
||||||
- Password change with old password verification
|
- Password change with old password verification
|
||||||
- User profile updates
|
- User profile updates
|
||||||
- gRPC service definition: `services/identity/api/identity.proto`
|
- User management API endpoints:
|
||||||
- User management endpoints (gRPC):
|
- `POST /api/v1/users` - Register new user
|
||||||
- `CreateUser(user)` - Register new user
|
- `GET /api/v1/users/:id` - Get user profile (authorized)
|
||||||
- `GetUser(id)` - Get user profile
|
- `PUT /api/v1/users/:id` - Update user profile (authorized)
|
||||||
- `UpdateUser(id, user)` - Update user profile
|
- `DELETE /api/v1/users/:id` - Delete user (admin only)
|
||||||
- `DeleteUser(id)` - Delete user (admin only)
|
- `POST /api/v1/users/verify-email` - Verify email with token
|
||||||
- `GetUserByEmail(email)` - Get user by email
|
- `POST /api/v1/users/reset-password` - Request password reset
|
||||||
- `VerifyEmail(token)` - Verify email with token
|
- `POST /api/v1/users/change-password` - Change password
|
||||||
- `RequestPasswordReset(email)` - Request password reset
|
- Integration with email notification system (Epic 5)
|
||||||
- `ResetPassword(token, new_password)` - Reset password
|
|
||||||
- `ChangePassword(user_id, old_password, new_password)` - Change password
|
|
||||||
- Database connection: Own connection pool and schema (`identity_schema`)
|
|
||||||
- Ent schema: User entity in `services/identity/ent/schema/user.go`
|
|
||||||
- Service registration: Register with service registry
|
|
||||||
- Integration with email notification system (Epic 5) via event bus
|
|
||||||
|
|
||||||
**Acceptance Criteria:**
|
**Acceptance Criteria:**
|
||||||
- Identity Service is independently deployable
|
- Users can register with email and password
|
||||||
- Service has own entry point (`cmd/identity-service/`)
|
|
||||||
- gRPC server starts and serves user management requests
|
|
||||||
- Users can register via gRPC with email and password
|
|
||||||
- Passwords are securely hashed
|
- Passwords are securely hashed
|
||||||
- Email verification tokens are generated and validated
|
- Email verification tokens are generated and validated
|
||||||
- Password reset flow works end-to-end
|
- Password reset flow works end-to-end
|
||||||
- Users can update their profiles via gRPC
|
- Users can update their profiles
|
||||||
- Service registers with service registry
|
- User operations require proper authentication
|
||||||
- Service has own database connection and schema
|
- All user actions are audited
|
||||||
- User entity is properly defined in Ent schema
|
|
||||||
|
|
||||||
#### 2.3 Role-Based Access Control (RBAC) System
|
#### 2.3 Role-Based Access Control (RBAC) System
|
||||||
**Goal:** Implement a complete RBAC system with permissions, role management, and authorization middleware.
|
**Goal:** Implement a complete RBAC system with permissions, role management, and authorization middleware.
|
||||||
@@ -1522,13 +1449,11 @@ This plan breaks down the implementation into **8 epics**, each with specific de
|
|||||||
## Epic 8: Advanced Features & Polish (Week 9-10, Optional)
|
## Epic 8: Advanced Features & Polish (Week 9-10, Optional)
|
||||||
|
|
||||||
### Objectives
|
### Objectives
|
||||||
- Add advanced features (OIDC, GraphQL)
|
- Add advanced features (OIDC, GraphQL, API Gateway)
|
||||||
- Performance optimization
|
- Performance optimization
|
||||||
- Additional sample feature services
|
- Additional sample modules
|
||||||
- Final polish and bug fixes
|
- Final polish and bug fixes
|
||||||
|
|
||||||
**Note:** API Gateway is now in Epic 1 (Story 1.8) as core infrastructure, not an advanced feature.
|
|
||||||
|
|
||||||
### Tasks
|
### Tasks
|
||||||
|
|
||||||
#### 8.1 OpenID Connect (OIDC) Support
|
#### 8.1 OpenID Connect (OIDC) Support
|
||||||
@@ -1554,26 +1479,30 @@ This plan breaks down the implementation into **8 epics**, each with specific de
|
|||||||
- Add authorization checks
|
- Add authorization checks
|
||||||
- [ ] Add GraphQL endpoint: `POST /graphql`
|
- [ ] Add GraphQL endpoint: `POST /graphql`
|
||||||
|
|
||||||
#### 8.3 Additional Sample Feature Services
|
#### 8.3 API Gateway Features
|
||||||
- [ ] Create Notification Service (`cmd/notification-service/`):
|
- [ ] Add request/response transformation
|
||||||
- Service entry point, gRPC server
|
- [ ] Add API key authentication
|
||||||
|
- [ ] Add request routing rules
|
||||||
|
- [ ] Add API versioning support
|
||||||
|
|
||||||
|
#### 8.4 Additional Sample Modules
|
||||||
|
- [ ] Create `modules/notification/`:
|
||||||
- Email templates
|
- Email templates
|
||||||
- Notification preferences
|
- Notification preferences
|
||||||
- Notification history
|
- Notification history
|
||||||
- [ ] Create Analytics Service (`cmd/analytics-service/`):
|
- [ ] Create `modules/analytics/`:
|
||||||
- Service entry point, gRPC server
|
|
||||||
- Event tracking
|
- Event tracking
|
||||||
- Analytics dashboard API
|
- Analytics dashboard API
|
||||||
- Export functionality
|
- Export functionality
|
||||||
|
|
||||||
#### 8.4 Performance Optimization
|
#### 8.5 Performance Optimization
|
||||||
- [ ] Add database query caching
|
- [ ] Add database query caching
|
||||||
- [ ] Optimize N+1 queries
|
- [ ] Optimize N+1 queries
|
||||||
- [ ] Add response caching (Redis)
|
- [ ] Add response caching (Redis)
|
||||||
- [ ] Implement connection pooling optimizations
|
- [ ] Implement connection pooling optimizations
|
||||||
- [ ] Add database read replicas support
|
- [ ] Add database read replicas support
|
||||||
|
|
||||||
#### 8.5 Internationalization (i18n)
|
#### 8.6 Internationalization (i18n)
|
||||||
- [ ] Install i18n library
|
- [ ] Install i18n library
|
||||||
- [ ] Add locale detection:
|
- [ ] Add locale detection:
|
||||||
- From Accept-Language header
|
- From Accept-Language header
|
||||||
@@ -1581,7 +1510,7 @@ This plan breaks down the implementation into **8 epics**, each with specific de
|
|||||||
- [ ] Create message catalogs
|
- [ ] Create message catalogs
|
||||||
- [ ] Add translation support for error messages
|
- [ ] Add translation support for error messages
|
||||||
|
|
||||||
#### 8.6 Final Polish
|
#### 8.7 Final Polish
|
||||||
- [ ] Code review and refactoring
|
- [ ] Code review and refactoring
|
||||||
- [ ] Bug fixes
|
- [ ] Bug fixes
|
||||||
- [ ] Performance profiling
|
- [ ] Performance profiling
|
||||||
@@ -1591,7 +1520,7 @@ This plan breaks down the implementation into **8 epics**, each with specific de
|
|||||||
### Deliverables
|
### Deliverables
|
||||||
- ✅ OIDC support (optional)
|
- ✅ OIDC support (optional)
|
||||||
- ✅ GraphQL API (optional)
|
- ✅ GraphQL API (optional)
|
||||||
- ✅ Additional sample feature services (Notification, Analytics)
|
- ✅ Additional sample modules
|
||||||
- ✅ Performance optimizations
|
- ✅ Performance optimizations
|
||||||
- ✅ Final polish
|
- ✅ Final polish
|
||||||
|
|
||||||
|
|||||||
@@ -1,13 +1,13 @@
|
|||||||
# Go‑Platform Boilerplate Play‑book
|
# Go‑Platform Boilerplate Play‑book
|
||||||
**“Plug‑in‑friendly SaaS/Enterprise Platform – Go Edition”**
|
**“Plug‑in‑friendly SaaS/Enterprise Platform – Go Edition”**
|
||||||
|
|
||||||
## 1 ARCHITECTURAL IMPERATIVES (Go‑flavoured)
|
## 1️⃣ ARCHITECTURAL IMPERATIVES (Go‑flavoured)
|
||||||
|
|
||||||
| Principle | Go‑specific rationale | Enforcement Technique |
|
| Principle | Go‑specific rationale | Enforcement Technique |
|
||||||
|-----------|-----------------------|------------------------|
|
|-----------|-----------------------|------------------------|
|
||||||
| **Hexagonal Architecture** | Go’s package‑level visibility (`internal/`) naturally creates a *boundary* between core and plug‑ins. | Keep all **domain** code in `internal/domain`, expose only **interfaces** in `pkg/`. |
|
| **Clean / Hexagonal Architecture** | Go’s package‑level visibility (`internal/`) naturally creates a *boundary* between core and plug‑ins. | Keep all **domain** code in `internal/domain`, expose only **interfaces** in `pkg/`. |
|
||||||
| **Dependency Injection (DI) via Constructors** | Go avoids reflection‑heavy containers; compile‑time wiring is preferred. | Use **uber‑go/fx** (runtime graph) *or* **uber‑go/dig** for optional runtime DI. For a lighter weight solution, use plain **constructor injection** with a small **registry**. |
|
| **Dependency Injection (DI) via Constructors** | Go avoids reflection‑heavy containers; compile‑time wiring is preferred. | Use **uber‑go/fx** (runtime graph) *or* **uber‑go/dig** for optional runtime DI. For a lighter weight solution, use plain **constructor injection** with a small **registry**. |
|
||||||
| **microMicroservices Architecture** | Each service is independently deployable from day one. Services communicate via gRPC/HTTP through service clients. | Each service has its own entry point (`cmd/{service}/`), Go module (`go.mod`), database connection, and deployment. Services discover each other via Consul service registry. |
|
| **Modular Monolith → Micro‑service‑ready** | A single binary is cheap in Go; later you can extract modules into separate services without breaking APIs. | Each module lives in its own Go **module** (`go.mod`) under `./modules/*`. The core loads them via the **Go plugin** system *or* static registration (preferred for CI stability). |
|
||||||
| **Plugin‑first design** | Go’s `plugin` package allows runtime loading of compiled `.so` files (Linux/macOS). | Provide an **IModule** interface and a **loader** that discovers `*.so` files (or compiled‑in modules for CI). |
|
| **Plugin‑first design** | Go’s `plugin` package allows runtime loading of compiled `.so` files (Linux/macOS). | Provide an **IModule** interface and a **loader** that discovers `*.so` files (or compiled‑in modules for CI). |
|
||||||
| **API‑First (OpenAPI + gin/gorilla)** | Guarantees language‑agnostic contracts. | Generate server stubs from an `openapi.yaml` stored in `api/`. |
|
| **API‑First (OpenAPI + gin/gorilla)** | Guarantees language‑agnostic contracts. | Generate server stubs from an `openapi.yaml` stored in `api/`. |
|
||||||
| **Security‑by‑Design** | Go’s static typing makes it easy to keep auth data out of the request flow. | Central middleware for JWT verification + context‑based user propagation. |
|
| **Security‑by‑Design** | Go’s static typing makes it easy to keep auth data out of the request flow. | Central middleware for JWT verification + context‑based user propagation. |
|
||||||
@@ -18,51 +18,29 @@
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 2 CORE KERNEL (Infrastructure Only)
|
## 2️⃣ CORE KERNEL (What every Go‑platform must ship)
|
||||||
|
|
||||||
The core kernel provides foundational infrastructure that all services depend on. It contains **no business logic**.
|
| Module | Public Interfaces (exported from `pkg/`) | Recommended Packages | Brief Implementation Sketch |
|
||||||
|
|
||||||
| Component | Public Interfaces (exported from `pkg/`) | Recommended Packages | Brief Implementation Sketch |
|
|
||||||
|--------|-------------------------------------------|----------------------|------------------------------|
|
|--------|-------------------------------------------|----------------------|------------------------------|
|
||||||
| **Config** | `type ConfigProvider interface { Get(key string) any; Unmarshal(v any) error }` | `github.com/spf13/viper` | Load defaults (`config/default.yaml`), then env overrides, then optional secret‑store. |
|
| **Config** | `type ConfigProvider interface { Get(key string) any; Unmarshal(v any) error }` | `github.com/spf13/viper` | Load defaults (`config/default.yaml`), then env overrides, then optional secret‑store. |
|
||||||
| **Logger** | `type Logger interface { Debug(msg string, fields ...Field); Info(...); Error(...); With(fields ...Field) Logger }` | `go.uber.org/zap` (or `zerolog`) | Global logger is created in each service's `cmd/{service}/main.go`; exported via `pkg/logger`. |
|
| **Logger** | `type Logger interface { Debug(msg string, fields ...Field); Info(...); Error(...); With(fields ...Field) Logger }` | `go.uber.org/zap` (or `zerolog`) | Global logger is created in `cmd/main.go`; exported via `pkg/logger`. |
|
||||||
| **DI / Service Registry** | `type Container interface { Provide(constructor any) error; Invoke(fn any) error }` | `go.uber.org/fx` (for lifecycle) | Each service creates its own `fx.New()` container, registers service-specific services. |
|
| **DI / Service Registry** | `type Container interface { Provide(constructor any) error; Invoke(fn any) error }` | `go.uber.org/dig` (or `fx` for lifecycle) | Core creates a `dig.New()` container, registers core services, then calls `container.Invoke(app.Start)`. |
|
||||||
| **Health & Metrics** | `type HealthChecker interface { Check(ctx context.Context) error }` | `github.com/prometheus/client_golang/prometheus`, `github.com/heptiolabs/healthcheck` | Each service exposes `/healthz`, `/ready`, `/metrics`. |
|
| **Health & Metrics** | `type HealthChecker interface { Check(ctx context.Context) error }` | `github.com/prometheus/client_golang/prometheus`, `github.com/heptiolabs/healthcheck` | Expose `/healthz`, `/ready`, `/metrics`. |
|
||||||
| **Error Bus** | `type ErrorPublisher interface { Publish(err error) }` | Simple channel‐based implementation + optional Sentry (`github.com/getsentry/sentry-go`) | Each service registers its own `ErrorBus`. |
|
| **Error Bus** | `type ErrorPublisher interface { Publish(err error) }` | Simple channel‐based implementation + optional Sentry (`github.com/getsentry/sentry-go`) | Core registers a singleton `ErrorBus`. |
|
||||||
| **Service Registry** | `type ServiceRegistry interface { Register(ctx, service) error; Discover(ctx, name) ([]Service, error) }` | `github.com/hashicorp/consul/api` | Consul-based service discovery. Services register on startup, clients discover via registry. |
|
| **Auth (JWT + OIDC)** | `type Authenticator interface { GenerateToken(userID string, roles []string) (string, error); VerifyToken(token string) (*TokenClaims, error) }` | `github.com/golang-jwt/jwt/v5`, `github.com/coreos/go-oidc` | Token claims embed `sub`, `roles`, `tenant_id`. Middleware adds `User` to `context.Context`. |
|
||||||
| **Observability** | `type Tracer interface { StartSpan(ctx, name) (Span, context.Context) }` | `go.opentelemetry.io/otel` | OpenTelemetry integration for distributed tracing across services. |
|
| **Authorization (RBAC/ABAC)** | `type Authorizer interface { Authorize(ctx context.Context, perm Permission) error }` | Custom DSL, `github.com/casbin/casbin/v2` (optional) | Permission format: `"module.resource.action"`; core ships a simple in‑memory resolver and a `casbin` adapter. |
|
||||||
| **Event Bus** | `type EventBus interface { Publish(ctx context.Context, ev Event) error; Subscribe(topic string, handler EventHandler) }` | `github.com/segmentio/kafka-go` | Kafka-based event bus for asynchronous cross-service communication. |
|
| **Audit** | `type Auditor interface { Record(ctx context.Context, act AuditAction) error }` | Write to append‑only table (Postgres) or Elastic via `olivere/elastic` | Audits include `actorID`, `action`, `targetID`, `metadata`. |
|
||||||
| **Scheduler / Background Jobs** | `type Scheduler interface { Cron(spec string, job JobFunc) error; Enqueue(q string, payload any) error }` | `github.com/robfig/cron/v3`, `github.com/hibiken/asynq` (Redis‑backed) | Shared infrastructure for background jobs. |
|
| **Event Bus** | `type EventBus interface { Publish(ctx context.Context, ev Event) error; Subscribe(topic string, handler EventHandler) }` | `github.com/segmentio/kafka-go` (for production) + in‑process fallback | Core ships an **in‑process bus** used by tests and a **Kafka bus** for real deployments. |
|
||||||
| **Notification** | `type Notifier interface { Send(ctx context.Context, n Notification) error }` | `github.com/go-mail/mail` (SMTP), `github.com/aws/aws-sdk-go-v2/service/ses` | Shared infrastructure for notifications. |
|
| **Persistence (Repository)** | `type UserRepo interface { FindByID(id string) (*User, error); Create(u *User) error; … }` | `entgo.io/ent` (code‑gen ORM) **or** `gorm.io/gorm` | Core provides an `EntClient` wrapper that implements all core repos. |
|
||||||
| **Multitenancy (optional)** | `type TenantResolver interface { Resolve(ctx context.Context) (tenantID string, err error) }` | Header/ sub‑domain parser + JWT claim scanner | Tenant ID is stored in request context and automatically added to SQL queries via Ent's `Client` interceptor. |
|
| **Scheduler / Background Jobs** | `type Scheduler interface { Cron(spec string, job JobFunc) error; Enqueue(q string, payload any) error }` | `github.com/robfig/cron/v3`, `github.com/hibiken/asynq` (Redis‑backed) | Expose a `JobRegistry` where modules can register periodic jobs. |
|
||||||
|
| **Notification** | `type Notifier interface { Send(ctx context.Context, n Notification) error }` | `github.com/go-mail/mail` (SMTP), `github.com/aws/aws-sdk-go-v2/service/ses`, `github.com/IBM/sarama` (for push) | Core supplies an `EmailNotifier` and a `WebhookNotifier`. |
|
||||||
|
| **Multitenancy (optional)** | `type TenantResolver interface { Resolve(ctx context.Context) (tenantID string, err error) }` | Header/ sub‑domain parser + JWT claim scanner | Tenant ID is stored in request context and automatically added to SQL queries via Ent’s `Client` interceptor. |
|
||||||
|
|
||||||
## 2.1 CORE SERVICES (Independent Microservices)
|
All *public* interfaces live under `pkg/` so that plug‑ins can import them without pulling in implementation details. The concrete implementations stay in `internal/` (or separate go.mod modules) and are **registered with the container** during bootstrap.
|
||||||
|
|
||||||
Core business services are implemented as separate, independently deployable services:
|
|
||||||
|
|
||||||
| Service | Entry Point | Responsibilities | Service Client Interface |
|
|
||||||
|--------|-------------|------------------|-------------------------|
|
|
||||||
| **Auth Service** | `cmd/auth-service/` | JWT token generation/validation, authentication | `AuthServiceClient` in `pkg/services/auth.go` |
|
|
||||||
| **Identity Service** | `cmd/identity-service/` | User CRUD, password management, email verification | `IdentityServiceClient` in `pkg/services/identity.go` |
|
|
||||||
| **Authz Service** | `cmd/authz-service/` | Permission resolution, RBAC/ABAC authorization | `AuthzServiceClient` in `pkg/services/authz.go` |
|
|
||||||
| **Audit Service** | `cmd/audit-service/` | Audit logging, immutable audit records | `AuditServiceClient` in `pkg/services/audit.go` |
|
|
||||||
| **API Gateway** | `cmd/api-gateway/` | Request routing, authentication, rate limiting, CORS | N/A (entry point) |
|
|
||||||
|
|
||||||
Each service:
|
|
||||||
|
|
||||||
- Has its own `go.mod` (or shared workspace)
|
|
||||||
- Manages its own database connection pool and schema
|
|
||||||
- Exposes gRPC server (and optional HTTP)
|
|
||||||
- Registers with Consul service registry
|
|
||||||
- Uses service clients for inter-service communication
|
|
||||||
|
|
||||||
All *public* interfaces live under `pkg/` so that services can import them without pulling in implementation details. The concrete implementations stay in `internal/` (for core kernel) or `services/{service}/internal/` (for service implementations) and are **registered with the container** during service bootstrap.
|
|
||||||
|
|
||||||
**Note:** Business logic services (Auth, Identity, Authz, Audit) are NOT in the core kernel. They are separate services implemented in Epic 2.
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 3 MODULE (PLUGIN) FRAMEWORK
|
## 3️⃣ MODULE (PLUGIN) FRAMEWORK
|
||||||
|
|
||||||
### 3.1 Interface that every module must implement
|
### 3.1 Interface that every module must implement
|
||||||
|
|
||||||
@@ -189,21 +167,13 @@ A **code‑gen** tool (`go generate ./...`) can scan each module’s `module.yam
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 4 SAMPLE FEATURE SERVICE – **Blog Service**
|
## 4️⃣ SAMPLE FEATURE MODULE – **Blog**
|
||||||
|
|
||||||
Each feature module is implemented as an independent service:
|
|
||||||
|
|
||||||
```
|
```
|
||||||
cmd/
|
modules/
|
||||||
└─ blog-service/
|
|
||||||
└─ main.go # Service entry point
|
|
||||||
|
|
||||||
services/
|
|
||||||
└─ blog/
|
└─ blog/
|
||||||
├─ go.mod # Service dependencies
|
├─ go.mod # (module github.com/yourorg/blog)
|
||||||
├─ module.yaml # Service manifest
|
├─ module.yaml
|
||||||
├─ api/
|
|
||||||
│ └─ blog.proto # gRPC service definition
|
|
||||||
├─ internal/
|
├─ internal/
|
||||||
│ ├─ api/
|
│ ├─ api/
|
||||||
│ │ └─ handler.go
|
│ │ └─ handler.go
|
||||||
@@ -237,165 +207,74 @@ routes:
|
|||||||
permission: blog.post.read
|
permission: blog.post.read
|
||||||
```
|
```
|
||||||
|
|
||||||
### 4.2 Service Entry Point
|
### 4.2 Go implementation
|
||||||
|
|
||||||
```go
|
```go
|
||||||
// cmd/blog-service/main.go
|
// pkg/module.go
|
||||||
package main
|
package blog
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"github.com/yourorg/platform/pkg/module"
|
||||||
"github.com/yourorg/platform/internal/config"
|
|
||||||
"github.com/yourorg/platform/internal/di"
|
|
||||||
"github.com/yourorg/platform/services/blog/internal/api"
|
|
||||||
"github.com/yourorg/platform/services/blog/internal/service"
|
|
||||||
"go.uber.org/fx"
|
"go.uber.org/fx"
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
type BlogModule struct{}
|
||||||
cfg := config.Load()
|
|
||||||
|
func (b BlogModule) Name() string { return "blog" }
|
||||||
fx.New(
|
|
||||||
// Core kernel services
|
func (b BlogModule) Init() fx.Option {
|
||||||
di.CoreModule(cfg),
|
return fx.Options(
|
||||||
|
// Register repository implementation
|
||||||
// Blog service implementation
|
fx.Provide(NewPostRepo),
|
||||||
fx.Provide(service.NewPostService),
|
|
||||||
fx.Provide(service.NewPostRepo),
|
// Register service layer
|
||||||
|
fx.Provide(NewPostService),
|
||||||
// gRPC server
|
|
||||||
fx.Provide(api.NewGRPCServer),
|
// Register HTTP handlers (using Gin)
|
||||||
|
fx.Invoke(RegisterHandlers),
|
||||||
// Service registry
|
|
||||||
fx.Provide(di.ProvideServiceRegistry),
|
// Register permissions (optional – just for documentation)
|
||||||
|
fx.Invoke(RegisterPermissions),
|
||||||
// Start service
|
)
|
||||||
fx.Invoke(startService),
|
|
||||||
).Run()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func startService(lc fx.Lifecycle, server *api.GRPCServer, registry registry.ServiceRegistry) {
|
func (b BlogModule) Migrations() []func(*ent.Client) error {
|
||||||
lc.Append(fx.Hook{
|
// Ent migration generated in internal/ent/migrate
|
||||||
OnStart: func(ctx context.Context) error {
|
return []func(*ent.Client) error{
|
||||||
// Register with Consul
|
func(c *ent.Client) error { return c.Schema.Create(context.Background()) },
|
||||||
registry.Register(ctx, ®istry.ServiceInstance{
|
}
|
||||||
ID: "blog-service-1",
|
|
||||||
Name: "blog-service",
|
|
||||||
Address: "localhost",
|
|
||||||
Port: 8091,
|
|
||||||
})
|
|
||||||
|
|
||||||
// Start gRPC server
|
|
||||||
return server.Start()
|
|
||||||
},
|
|
||||||
OnStop: func(ctx context.Context) error {
|
|
||||||
registry.Deregister(ctx, "blog-service-1")
|
|
||||||
return server.Stop()
|
|
||||||
},
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Export a variable for the plugin loader
|
||||||
|
var Module BlogModule
|
||||||
```
|
```
|
||||||
|
|
||||||
### 4.3 Service Implementation
|
**Handler registration (Gin example)**
|
||||||
|
|
||||||
```go
|
```go
|
||||||
// services/blog/internal/service/post_service.go
|
// internal/api/handler.go
|
||||||
package service
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"github.com/yourorg/platform/pkg/services"
|
|
||||||
"github.com/yourorg/platform/services/blog/internal/domain"
|
|
||||||
)
|
|
||||||
|
|
||||||
type PostService struct {
|
|
||||||
repo *domain.PostRepo
|
|
||||||
authzClient services.AuthzServiceClient
|
|
||||||
identityClient services.IdentityServiceClient
|
|
||||||
auditClient services.AuditServiceClient
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewPostService(
|
|
||||||
repo *domain.PostRepo,
|
|
||||||
authzClient services.AuthzServiceClient,
|
|
||||||
identityClient services.IdentityServiceClient,
|
|
||||||
auditClient services.AuditServiceClient,
|
|
||||||
) *PostService {
|
|
||||||
return &PostService{
|
|
||||||
repo: repo,
|
|
||||||
authzClient: authzClient,
|
|
||||||
identityClient: identityClient,
|
|
||||||
auditClient: auditClient,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *PostService) CreatePost(ctx context.Context, req *CreatePostRequest) (*Post, error) {
|
|
||||||
// Check permission via Authz Service
|
|
||||||
if err := s.authzClient.Authorize(ctx, "blog.post.create"); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get user info via Identity Service
|
|
||||||
user, err := s.identityClient.GetUser(ctx, req.AuthorID)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create post
|
|
||||||
post, err := s.repo.Create(ctx, &domain.Post{
|
|
||||||
Title: req.Title,
|
|
||||||
Content: req.Content,
|
|
||||||
AuthorID: user.ID,
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Audit log via Audit Service
|
|
||||||
s.auditClient.Record(ctx, &services.AuditAction{
|
|
||||||
ActorID: user.ID,
|
|
||||||
Action: "blog.post.create",
|
|
||||||
TargetID: post.ID,
|
|
||||||
})
|
|
||||||
|
|
||||||
return post, nil
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### 4.4 gRPC Handler
|
|
||||||
|
|
||||||
```go
|
|
||||||
// services/blog/internal/api/handler.go
|
|
||||||
package api
|
package api
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"github.com/gin-gonic/gin"
|
||||||
"github.com/yourorg/platform/services/blog/api/pb"
|
"github.com/yourorg/blog/internal/service"
|
||||||
"github.com/yourorg/platform/services/blog/internal/service"
|
"github.com/yourorg/platform/pkg/perm"
|
||||||
|
"github.com/yourorg/platform/pkg/auth"
|
||||||
)
|
)
|
||||||
|
|
||||||
type BlogServer struct {
|
func RegisterHandlers(r *gin.Engine, svc *service.PostService, authz auth.Authorizer) {
|
||||||
pb.UnimplementedBlogServiceServer
|
grp := r.Group("/api/v1/blog")
|
||||||
service *service.PostService
|
grp.Use(auth.AuthMiddleware()) // verifies JWT, injects user in context
|
||||||
}
|
|
||||||
|
|
||||||
func (s *BlogServer) CreatePost(ctx context.Context, req *pb.CreatePostRequest) (*pb.CreatePostResponse, error) {
|
// POST /posts
|
||||||
post, err := s.service.CreatePost(ctx, &service.CreatePostRequest{
|
grp.POST("/posts", func(c *gin.Context) {
|
||||||
Title: req.Title,
|
if err := authz.Authorize(c.Request.Context(), perm.BlogPostCreate); err != nil {
|
||||||
Content: req.Content,
|
c.JSON(403, gin.H{"error": "forbidden"})
|
||||||
AuthorID: req.AuthorId,
|
return
|
||||||
|
}
|
||||||
|
// decode request, call svc.Create, return 201…
|
||||||
})
|
})
|
||||||
if err != nil {
|
// GET /posts/:id (similar)
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return &pb.CreatePostResponse{
|
|
||||||
Post: &pb.Post{
|
|
||||||
Id: post.ID,
|
|
||||||
Title: post.Title,
|
|
||||||
Content: post.Content,
|
|
||||||
},
|
|
||||||
}, nil
|
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -433,7 +312,7 @@ func (r *PostRepo) Create(ctx context.Context, p *Post) (*Post, error) {
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 5 INFRASTRUCTURE ADAPTERS (swap‑able, per‑environment)
|
## 5️⃣ INFRASTRUCTURE ADAPTERS (swap‑able, per‑environment)
|
||||||
|
|
||||||
| Concern | Implementation (Go) | Where it lives |
|
| Concern | Implementation (Go) | Where it lives |
|
||||||
|---------|---------------------|----------------|
|
|---------|---------------------|----------------|
|
||||||
@@ -449,7 +328,7 @@ All adapters expose an **interface** in `pkg/infra/…` and are registered in th
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 6 OBSERVABILITY STACK
|
## 6️⃣ OBSERVABILITY STACK
|
||||||
|
|
||||||
| Layer | Library | What it does |
|
| Layer | Library | What it does |
|
||||||
|-------|---------|--------------|
|
|-------|---------|--------------|
|
||||||
@@ -493,7 +372,7 @@ func PromMetrics() gin.HandlerFunc {
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 7 CONFIGURATION & ENVIRONMENT
|
## 7️⃣ CONFIGURATION & ENVIRONMENT
|
||||||
|
|
||||||
```
|
```
|
||||||
config/
|
config/
|
||||||
@@ -526,7 +405,7 @@ All services receive a `*Config` via DI.
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 8 CI / CD PIPELINE (GitHub Actions)
|
## 8️⃣ CI / CD PIPELINE (GitHub Actions)
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
name: CI
|
name: CI
|
||||||
@@ -590,7 +469,7 @@ jobs:
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 9 TESTING STRATEGY
|
## 9️⃣ TESTING STRATEGY
|
||||||
|
|
||||||
| Test type | Tools | Typical coverage |
|
| Test type | Tools | Typical coverage |
|
||||||
|-----------|-------|------------------|
|
|-----------|-------|------------------|
|
||||||
@@ -644,7 +523,7 @@ func TestCreatePost_Integration(t *testing.T) {
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 10 COMMON PITFALLS & SOLUTIONS (Go‑centric)
|
## 10️⃣ COMMON PITFALLS & SOLUTIONS (Go‑centric)
|
||||||
|
|
||||||
| Pitfall | Symptom | Remedy |
|
| Pitfall | Symptom | Remedy |
|
||||||
|---------|----------|--------|
|
|---------|----------|--------|
|
||||||
@@ -661,7 +540,7 @@ func TestCreatePost_Integration(t *testing.T) {
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 11 QUICK‑START STEPS (What to code first)
|
## 11️⃣ QUICK‑START STEPS (What to code first)
|
||||||
|
|
||||||
1. **Bootstrap repo**
|
1. **Bootstrap repo**
|
||||||
```bash
|
```bash
|
||||||
@@ -707,7 +586,7 @@ After step 10 you have a **complete, production‑grade scaffolding** that:
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 12 REFERENCE IMPLEMENTATION (public)
|
## 12️⃣ REFERENCE IMPLEMENTATION (public)
|
||||||
|
|
||||||
If you prefer to start from a **real open‑source baseline**, check out the following community projects that already adopt most of the ideas above:
|
If you prefer to start from a **real open‑source baseline**, check out the following community projects that already adopt most of the ideas above:
|
||||||
|
|
||||||
@@ -723,7 +602,7 @@ Fork one, strip the business logic, and rename the packages to match *your* `git
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 13 FINAL CHECKLIST (before you ship)
|
## 13️⃣ FINAL CHECKLIST (before you ship)
|
||||||
|
|
||||||
- [ ] Core modules compiled & registered in `internal/di`.
|
- [ ] Core modules compiled & registered in `internal/di`.
|
||||||
- [ ] `module.IModule` interface and static registry in place.
|
- [ ] `module.IModule` interface and static registry in place.
|
||||||
@@ -738,4 +617,4 @@ Fork one, strip the business logic, and rename the packages to match *your* `git
|
|||||||
- [ ] Sample plug‑in (Blog) builds, loads, registers routes, and passes integration test.
|
- [ ] Sample plug‑in (Blog) builds, loads, registers routes, and passes integration test.
|
||||||
- [ ] Documentation: `README.md`, `docs/architecture.md`, `docs/extension-points.md`.
|
- [ ] Documentation: `README.md`, `docs/architecture.md`, `docs/extension-points.md`.
|
||||||
|
|
||||||
> **Congratulations!** You now have a **robust, extensible Go platform boilerplate** that can be the foundation for any SaaS, internal toolset, or micro‑service ecosystem you wish to build. Happy coding!
|
> **Congratulations!** You now have a **robust, extensible Go platform boilerplate** that can be the foundation for any SaaS, internal toolset, or micro‑service ecosystem you wish to build. Happy coding! 🚀
|
||||||
@@ -6,7 +6,7 @@
|
|||||||
|-----------|----------------------------------------|-------------------|
|
|-----------|----------------------------------------|-------------------|
|
||||||
| **Separation of Concerns (SoC)** | Keeps core services (auth, audit, config) independent from business modules. | Use **layered** or **hexagonal/clean‑architecture** boundaries. |
|
| **Separation of Concerns (SoC)** | Keeps core services (auth, audit, config) independent from business modules. | Use **layered** or **hexagonal/clean‑architecture** boundaries. |
|
||||||
| **Domain‑Driven Design (DDD) Bounded Contexts** | Allows each module to own its own model & rules while sharing a common identity kernel. | Define a **Core Context** (Identity, Security, Infrastructure) and **Feature Contexts** (Billing, CMS, Chat, …). |
|
| **Domain‑Driven Design (DDD) Bounded Contexts** | Allows each module to own its own model & rules while sharing a common identity kernel. | Define a **Core Context** (Identity, Security, Infrastructure) and **Feature Contexts** (Billing, CMS, Chat, …). |
|
||||||
| **microMicroservices Architecture** | Each service is independently deployable from day one. Services communicate via gRPC/HTTP through service clients. | Each service has its own entry point (`cmd/{service}/`), database connection, and deployment configuration. |
|
| **Modular Monolith → Micro‑service‑ready** | Start simple (single process) but keep each module in its own package so you can later split to services if needed. | Package each module as an **independent library** with its own **DI container‑module** and **routing**. |
|
||||||
| **Plug‑in / Extension‑point model** | Enables customers or internal teams to drop new features without touching core code. | Export **well‑defined interfaces** (e.g., `IUserProvider`, `IPermissionResolver`, `IModuleInitializer`). |
|
| **Plug‑in / Extension‑point model** | Enables customers or internal teams to drop new features without touching core code. | Export **well‑defined interfaces** (e.g., `IUserProvider`, `IPermissionResolver`, `IModuleInitializer`). |
|
||||||
| **API‑First** | Guarantees that any UI (web, mobile, CLI) can be built on top of the same contract. | Publish **OpenAPI/GraphQL schema** as part of the build artefact. |
|
| **API‑First** | Guarantees that any UI (web, mobile, CLI) can be built on top of the same contract. | Publish **OpenAPI/GraphQL schema** as part of the build artefact. |
|
||||||
| **Security‑by‑Design** | The platform will hold user credentials, roles and possibly PII. | Centralize **authentication**, **authorization**, **audit**, **rate‑limiting**, **CORS**, **CSP**, **secure defaults**. |
|
| **Security‑by‑Design** | The platform will hold user credentials, roles and possibly PII. | Centralize **authentication**, **authorization**, **audit**, **rate‑limiting**, **CORS**, **CSP**, **secure defaults**. |
|
||||||
@@ -50,17 +50,7 @@
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## CORE KERNEL (INFRASTRUCTURE ONLY)
|
## REQUIRED BASE MODULES (THE “CORE KERNEL”)
|
||||||
|
|
||||||
The core kernel provides foundational infrastructure services that all other services depend on. It contains **no business logic** and is purely infrastructure.
|
|
||||||
|
|
||||||
## CORE SERVICES (INDEPENDENT MICROSERVICES)
|
|
||||||
|
|
||||||
Core services provide business logic and are deployed as separate, independently scalable services. Each service has its own database connection, API endpoints, and deployment configuration.
|
|
||||||
|
|
||||||
## INFRASTRUCTURE ADAPTERS (SHARED SERVICES)
|
|
||||||
|
|
||||||
These adapters provide infrastructure capabilities that services can use.
|
|
||||||
|
|
||||||
| Module | Core responsibilities | Public API / Extension points |
|
| Module | Core responsibilities | Public API / Extension points |
|
||||||
|--------|-----------------------|--------------------------------|
|
|--------|-----------------------|--------------------------------|
|
||||||
@@ -138,65 +128,38 @@ These adapters provide infrastructure capabilities that services can use.
|
|||||||
```
|
```
|
||||||
/platform-root
|
/platform-root
|
||||||
│
|
│
|
||||||
├─ /cmd # ---- Service Entry Points ----
|
├─ /core # ---- Kernel / Base modules ----
|
||||||
│ ├─ /api-gateway # API Gateway service
|
│ ├─ /auth
|
||||||
│ │ └─ main.go
|
│ │ ├─ src/
|
||||||
│ ├─ /auth-service # Auth service
|
│ │ └─ package.json
|
||||||
│ │ └─ main.go
|
│ ├─ /identity
|
||||||
│ ├─ /identity-service # Identity service
|
│ ├─ /authorization
|
||||||
│ │ └─ main.go
|
│ ├─ /audit
|
||||||
│ ├─ /authz-service # Authorization service
|
│ ├─ /config
|
||||||
│ │ └─ main.go
|
│ ├─ /logging
|
||||||
│ ├─ /audit-service # Audit service
|
│ ├─ /metrics
|
||||||
│ │ └─ main.go
|
│ └─ index.ts (exports all core APIs)
|
||||||
│ └─ /blog-service # Blog feature service
|
|
||||||
│ └─ main.go
|
|
||||||
│
|
│
|
||||||
├─ /services # ---- Service Implementations ----
|
├─ /modules # ---- Feature plug‑ins ----
|
||||||
│ ├─ /auth/
|
│ ├─ /blog
|
||||||
│ │ ├─ internal/ # Service implementation
|
│ │ ├─ module.yaml # manifest
|
||||||
│ │ └─ api/ # gRPC/HTTP definitions
|
│ │ ├─ src/
|
||||||
│ ├─ /identity/
|
│ │ │ ├─ BlogController.ts
|
||||||
│ ├─ /authz/
|
│ │ │ ├─ BlogService.ts
|
||||||
│ ├─ /audit/
|
│ │ │ └─ BlogModule.ts (implements IModuleInitializer)
|
||||||
│ └─ /blog/
|
│ │ └─ package.json
|
||||||
│
|
|
||||||
├─ /internal # ---- Core Kernel (Infrastructure) ----
|
|
||||||
│ ├─ /config # Configuration management
|
|
||||||
│ ├─ /logger # Logging system
|
|
||||||
│ ├─ /di # Dependency injection
|
|
||||||
│ ├─ /health # Health checks
|
|
||||||
│ ├─ /metrics # Metrics collection
|
|
||||||
│ ├─ /observability # OpenTelemetry integration
|
|
||||||
│ ├─ /registry # Service registry
|
|
||||||
│ └─ /pluginloader # Module loader
|
|
||||||
│
|
|
||||||
├─ /pkg # ---- Public Interfaces ----
|
|
||||||
│ ├─ /config # ConfigProvider interface
|
|
||||||
│ ├─ /logger # Logger interface
|
|
||||||
│ ├─ /services # Service client interfaces
|
|
||||||
│ │ ├─ auth.go # AuthServiceClient
|
|
||||||
│ │ ├─ identity.go # IdentityServiceClient
|
|
||||||
│ │ ├─ authz.go # AuthzServiceClient
|
|
||||||
│ │ └─ audit.go # AuditServiceClient
|
|
||||||
│ └─ /module # IModule interface
|
|
||||||
│
|
|
||||||
├─ /modules # ---- Feature Services ----
|
|
||||||
│ ├─ /blog/
|
|
||||||
│ │ ├─ go.mod # Service module
|
|
||||||
│ │ ├─ module.yaml # Service manifest
|
|
||||||
│ │ ├─ internal/ # Service implementation
|
|
||||||
│ │ └─ pkg/
|
|
||||||
│ │ └─ module.go # IModule implementation
|
|
||||||
│ │
|
│ │
|
||||||
│ ├─ /billing/
|
│ ├─ /billing
|
||||||
│ └─ /chat/
|
│ └─ /chat
|
||||||
│
|
│
|
||||||
├─ /infra # ---- Infrastructure Adapters ----
|
├─ /infra # ---- Infrastructure adapters ----
|
||||||
|
│ ├─ /orm (typeorm/hibernate/EFCore etc.)
|
||||||
│ ├─ /cache (redis)
|
│ ├─ /cache (redis)
|
||||||
│ ├─ /queue (kafka)
|
│ ├─ /queue (rabbit/kafka)
|
||||||
│ └─ /storage (s3/azure‑blob)
|
│ └─ /storage (s3/azure‑blob)
|
||||||
│
|
│
|
||||||
|
├─ /gateway (optional API‑gateway layer)
|
||||||
|
│
|
||||||
├─ /scripts # build / lint / test helpers
|
├─ /scripts # build / lint / test helpers
|
||||||
│
|
│
|
||||||
├─ /ci
|
├─ /ci
|
||||||
@@ -205,51 +168,42 @@ These adapters provide infrastructure capabilities that services can use.
|
|||||||
├─ /docs
|
├─ /docs
|
||||||
│ └─ architecture.md
|
│ └─ architecture.md
|
||||||
│
|
│
|
||||||
├─ go.mod # Workspace root
|
├─ package.json (or pom.xml / go.mod)
|
||||||
└─ README.md
|
└─ README.md
|
||||||
```
|
```
|
||||||
|
|
||||||
### How Services Boot
|
### How it boots
|
||||||
|
|
||||||
Each service has its own entry point and bootstraps independently:
|
```ts
|
||||||
|
// platform-root/src/main.ts
|
||||||
|
import { createApp } from '@core/app';
|
||||||
|
import { loadModules } from '@core/module-loader';
|
||||||
|
import { CoreModule } from '@core';
|
||||||
|
|
||||||
```go
|
async function bootstrap() {
|
||||||
// cmd/auth-service/main.go
|
const app = await createApp();
|
||||||
func main() {
|
|
||||||
// 1️⃣ Load configuration
|
// 1️⃣ Load core kernel (DI, config, logger)
|
||||||
cfg := config.Load()
|
await app.register(CoreModule);
|
||||||
|
|
||||||
// 2️⃣ Initialize core kernel (DI, logger, metrics)
|
// 2️⃣ Dynamically discover all `module.yaml` under /modules
|
||||||
container := di.NewContainer(cfg)
|
const modules = await loadModules(__dirname + '/modules');
|
||||||
|
|
||||||
// 3️⃣ Register service implementations
|
// 3️⃣ Initialise each module (order can be defined in manifest)
|
||||||
container.Provide(NewAuthService)
|
for (const mod of modules) {
|
||||||
container.Provide(NewTokenProvider)
|
await mod.instance.init(app.builder, app.container);
|
||||||
|
}
|
||||||
// 4️⃣ Register gRPC server
|
|
||||||
container.Provide(NewGRPCServer)
|
// 4️⃣ Start HTTP / gRPC server
|
||||||
|
await app.listen(process.env.PORT || 3000);
|
||||||
// 5️⃣ Register with service registry
|
|
||||||
container.Provide(NewServiceRegistry)
|
|
||||||
|
|
||||||
// 6️⃣ Start service
|
|
||||||
container.Start()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// cmd/api-gateway/main.go
|
bootstrap().catch(err => {
|
||||||
func main() {
|
console.error('❌ Platform failed to start', err);
|
||||||
// API Gateway bootstraps similarly
|
process.exit(1);
|
||||||
// Routes requests to backend services via service discovery
|
});
|
||||||
}
|
|
||||||
```
|
```
|
||||||
|
|
||||||
Services communicate via service clients:
|
|
||||||
|
|
||||||
- All inter-service communication uses gRPC (primary) or HTTP (fallback)
|
|
||||||
- Service discovery via service registry
|
|
||||||
- Each service manages its own database connection
|
|
||||||
- Services can be deployed independently
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## KEY DECISIONS YOU MUST TAKE EARLY
|
## KEY DECISIONS YOU MUST TAKE EARLY
|
||||||
@@ -288,54 +242,41 @@ Services communicate via service clients:
|
|||||||
|
|
||||||
1. **Create the Core Kernel**
|
1. **Create the Core Kernel**
|
||||||
- Set up DI container, config loader, logger, health/metrics endpoint.
|
- Set up DI container, config loader, logger, health/metrics endpoint.
|
||||||
- Infrastructure only - no business logic.
|
- Scaffold `IUserRepository`, `IPermissionResolver`, `ITokenProvider`.
|
||||||
|
|
||||||
2. **Implement API Gateway**
|
2. **Implement Identity & Auth**
|
||||||
- Request routing to backend services.
|
- Choose JWT + Refresh + optional OpenID Connect.
|
||||||
- Authentication at edge, rate limiting, CORS.
|
- Add password hashing (bcrypt/argon2) and email verification flow.
|
||||||
- Integration with service discovery.
|
|
||||||
|
|
||||||
3. **Implement Core Services**
|
3. **Add Role/Permission Engine**
|
||||||
- **Identity Service**: User CRUD, password hashing, email verification.
|
- Simple RBAC matrix with an extensible `Permission` type.
|
||||||
- **Auth Service**: JWT token generation/validation, refresh tokens.
|
- Provide a UI admin UI (or API only) to manage roles.
|
||||||
- **Authz Service**: Permission resolution, RBAC/ABAC.
|
|
||||||
- **Audit Service**: Immutable audit logging.
|
|
||||||
|
|
||||||
4. **Set Up Service Communication**
|
4. **Set Up Event Bus & Audit**
|
||||||
- Define service client interfaces.
|
- Publish `user.created`, `role.granted` events.
|
||||||
- Implement gRPC clients (primary) and HTTP clients (fallback).
|
- Store audit entries in an append‑only table (or log to Elastic).
|
||||||
- Service registry for discovery.
|
|
||||||
|
|
||||||
5. **Set Up Event Bus & Infrastructure**
|
5. **Build the Module Loader**
|
||||||
- Kafka-based event bus.
|
- Scan `modules/*/module.yaml`, load via `require()`/classpath.
|
||||||
- Redis cache.
|
- Register each `IModuleInitializer`.
|
||||||
- Shared infrastructure adapters.
|
|
||||||
|
|
||||||
6. **Build the Module Loader**
|
6. **Create a Sample Feature Module** – e.g., **Blog**
|
||||||
- Scan `modules/*/module.yaml` for service modules.
|
|
||||||
- Register services with service registry.
|
|
||||||
- Manage service lifecycle.
|
|
||||||
|
|
||||||
7. **Create a Sample Feature Service** – e.g., **Blog Service**
|
|
||||||
- Own entry point (`cmd/blog-service/`).
|
|
||||||
- Own database connection and schema.
|
|
||||||
- Use service clients for Auth, Identity, Authz.
|
|
||||||
- Define its own entities (`Post`, `Comment`).
|
- Define its own entities (`Post`, `Comment`).
|
||||||
|
- Register routes (`/api/v1/blog/posts`).
|
||||||
|
- Declare required permissions (`blog.post.create`).
|
||||||
|
|
||||||
8. **Write Integration Tests**
|
7. **Write Integration Tests**
|
||||||
- Test service interactions via service clients.
|
- Spin up an in‑memory DB (SQLite or H2).
|
||||||
- Spin up services in Docker Compose.
|
- Load core + blog module, assert that a user without `blog.post.create` receives 403.
|
||||||
- Test cross-service communication.
|
|
||||||
|
|
||||||
9. **Add CI Pipeline**
|
8. **Add CI Pipeline**
|
||||||
- Build and test each service independently.
|
- Lint → Unit → Integration (Docker Compose with DB + Redis).
|
||||||
- Docker images for each service.
|
- On tag, publish `core` and `blog` packages to your private registry.
|
||||||
- Service deployment automation.
|
|
||||||
|
|
||||||
10. **Document Service Architecture**
|
9. **Document Extension Points**
|
||||||
- Service boundaries and responsibilities.
|
- Provide a **Developer Handbook** (README + `docs/extension-points.md`).
|
||||||
- Service client interfaces.
|
|
||||||
- Deployment and scaling guides.
|
10. **Iterate** – add Notification, Scheduler, Multitenancy, API‑Gateway as needed.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -357,14 +298,14 @@ Pick the stack you’re most comfortable with; the concepts stay identical.
|
|||||||
|
|
||||||
| Layer | Must‑have components | Why |
|
| Layer | Must‑have components | Why |
|
||||||
|-------|----------------------|-----|
|
|-------|----------------------|-----|
|
||||||
| **Core Kernel** | Config, Logger, DI, Health, Metrics, Error Bus, Observability | Foundation infrastructure for all services. |
|
| **Core Kernel** | Config, Logger, DI, Health, Metrics, Error Bus | Foundation for any module. |
|
||||||
| **API Gateway** | Request routing, authentication, rate limiting, CORS | Single entry point for all external traffic. |
|
| **Security** | Auth (JWT/OIDC), Authorization (RBAC + ABAC), Audit | Guarantees secure, traceable access. |
|
||||||
| **Core Services** | Identity, Auth, Authz, Audit services (separate services) | Independent, scalable security services. |
|
| **User & Role Management** | User CRUD, Password reset, Role ↔ Permission matrix | The “identity” piece everyone will reuse. |
|
||||||
| **Service Clients** | gRPC/HTTP clients, service discovery, service registry | Enables service-to-service communication. |
|
| **Extension System** | `IModuleInitializer`, `module.yaml`, EventBus, Permission DSL | Enables plug‑ins without touching core. |
|
||||||
| **Infrastructure Adapters** | Cache, Event Bus, Blob storage, Email/SMS, Scheduler | Shared infrastructure capabilities. |
|
| **Infrastructure Adapters** | DB repo, Cache, Queue, Blob storage, Email/SMS | Keeps core agnostic to any concrete tech. |
|
||||||
| **Observability** | Structured logs, Prometheus metrics, OpenTelemetry traces | Cross-service observability and monitoring. |
|
| **Observability** | Structured logs, Prometheus metrics, OpenTelemetry traces | You can monitor each module individually. |
|
||||||
| **DevOps Boilerplate** | CI pipelines, Dockerfiles, service deployment, Docs | Makes each service production‑ready. |
|
| **DevOps Boilerplate** | CI pipelines, Dockerfiles, Semantic‑release, Docs | Makes the framework production‑ready out‑of‑the‑box. |
|
||||||
| **Sample Feature Service** | (e.g., Blog Service) with own entry point, DB, service clients | Provides a reference implementation for future services. |
|
| **Sample Feature Module** | (e.g., Blog) to show how to add routes, permissions, DB entities | Provides a reference implementation for future developers. |
|
||||||
|
|
||||||
When you scaffold those pieces **once**, any downstream team can drop a new folder that follows the `module.yaml` contract, implement the initializer, add its own tables & APIs, and instantly get:
|
When you scaffold those pieces **once**, any downstream team can drop a new folder that follows the `module.yaml` contract, implement the initializer, add its own tables & APIs, and instantly get:
|
||||||
|
|
||||||
|
|||||||
@@ -16,35 +16,34 @@ Tasks are organized by epic and section. Each task file follows the naming conve
|
|||||||
- [0.5 Dependency Injection and Application Bootstrap](./epic0/0.5-di-and-bootstrap.md)
|
- [0.5 Dependency Injection and Application Bootstrap](./epic0/0.5-di-and-bootstrap.md)
|
||||||
|
|
||||||
## Epic 1: Core Kernel & Infrastructure
|
## Epic 1: Core Kernel & Infrastructure
|
||||||
- [1.1 Enhanced DI Container](./epic1/1.1-enhanced-di-container.md) - Core kernel services only
|
- [1.1 Enhanced DI Container](./epic1/1.1-enhanced-di-container.md)
|
||||||
- [1.2 Database Client Foundation](./epic1/1.2-database-layer.md) - Per-service database connections
|
- [1.2 Database Layer](./epic1/1.2-database-layer.md)
|
||||||
- [1.3 Health & Metrics System](./epic1/1.3-health-metrics-system.md)
|
- [1.3 Health & Metrics System](./epic1/1.3-health-metrics-system.md)
|
||||||
- [1.4 Error Handling](./epic1/1.4-error-handling.md)
|
- [1.4 Error Handling](./epic1/1.4-error-handling.md)
|
||||||
- [1.5 HTTP/gRPC Server Foundation](./epic1/1.5-http-server.md) - Server foundations for services
|
- [1.5 HTTP Server](./epic1/1.5-http-server.md)
|
||||||
- [1.6 OpenTelemetry](./epic1/1.6-opentelemetry.md) - Distributed tracing across services
|
- [1.6 OpenTelemetry](./epic1/1.6-opentelemetry.md)
|
||||||
- [1.7 Service Client Interfaces](./epic1/1.7-service-client-interfaces.md) - Service client interfaces
|
|
||||||
- [1.8 API Gateway Implementation](./epic1/1.8-api-gateway.md) - API Gateway as core infrastructure
|
|
||||||
- [Epic 1 Overview](./epic1/README.md)
|
- [Epic 1 Overview](./epic1/README.md)
|
||||||
|
|
||||||
## Epic 2: Core Services (Authentication & Authorization)
|
## Epic 2: Authentication & Authorization
|
||||||
- [2.1 Auth Service - JWT Authentication](./epic2/2.1-jwt-authentication.md) - Independent Auth Service
|
- [2.1 JWT Authentication System](./epic2/2.1-jwt-authentication.md)
|
||||||
- [2.2 Identity Service - User Management](./epic2/2.2-identity-management.md) - Independent Identity Service
|
- [2.2 Identity Management System](./epic2/2.2-identity-management.md)
|
||||||
- [2.3 Authz Service - Authorization & RBAC](./epic2/2.3-rbac-system.md) - Independent Authz Service
|
- [2.3 RBAC System](./epic2/2.3-rbac-system.md)
|
||||||
- [2.4 Role Management (Part of Authz Service)](./epic2/2.4-role-management.md) - Role management gRPC endpoints
|
- [2.4 Role Management API](./epic2/2.4-role-management.md)
|
||||||
- [2.5 Audit Service - Audit Logging](./epic2/2.5-audit-logging.md) - Independent Audit Service
|
- [2.5 Audit Logging System](./epic2/2.5-audit-logging.md)
|
||||||
- [2.6 Database Seeding](./epic2/2.6-database-seeding.md) - Per-service seeding
|
- [2.6 Database Seeding and Initialization](./epic2/2.6-database-seeding.md)
|
||||||
|
- [2.7 Service Client Interfaces](./epic2/2.7-service-abstraction-layer.md)
|
||||||
- [Epic 2 Overview](./epic2/README.md)
|
- [Epic 2 Overview](./epic2/README.md)
|
||||||
|
|
||||||
## Epic 3: Module Framework (Feature Services)
|
## Epic 3: Module Framework
|
||||||
- [3.1 Module System Interface](./epic3/3.1-module-system-interface.md) - Module interface for feature services
|
- [3.1 Module System Interface](./epic3/3.1-module-system-interface.md)
|
||||||
- [3.2 Permission Code Generation](./epic3/3.2-permission-code-generation.md)
|
- [3.2 Permission Code Generation](./epic3/3.2-permission-code-generation.md)
|
||||||
- [3.3 Service Loader](./epic3/3.3-module-loader.md) - Service initialization helpers
|
- [3.3 Module Loader](./epic3/3.3-module-loader.md)
|
||||||
- [3.4 Service Management CLI](./epic3/3.4-module-cli.md) - Service management CLI
|
- [3.4 Module CLI](./epic3/3.4-module-cli.md)
|
||||||
- [3.5 Service Registry Verification](./epic3/3.5-service-registry.md) - Verify Consul integration
|
- [3.5 Service Registry and Discovery](./epic3/3.5-service-registry.md)
|
||||||
- [Epic 3 Overview](./epic3/README.md)
|
- [Epic 3 Overview](./epic3/README.md)
|
||||||
|
|
||||||
## Epic 4: Sample Feature Service (Blog Service)
|
## Epic 4: Sample Feature Module (Blog)
|
||||||
- [4.1 Complete Blog Service](./epic4/4.1-blog-module.md) - Blog Service as reference implementation
|
- [4.1 Complete Blog Module](./epic4/4.1-blog-module.md)
|
||||||
- [Epic 4 Overview](./epic4/README.md)
|
- [Epic 4 Overview](./epic4/README.md)
|
||||||
|
|
||||||
## Epic 5: Infrastructure Adapters
|
## Epic 5: Infrastructure Adapters
|
||||||
@@ -54,7 +53,7 @@ Tasks are organized by epic and section. Each task file follows the naming conve
|
|||||||
- [5.4 Email Notification](./epic5/5.4-email-notification.md)
|
- [5.4 Email Notification](./epic5/5.4-email-notification.md)
|
||||||
- [5.5 Scheduler & Jobs](./epic5/5.5-scheduler-jobs.md)
|
- [5.5 Scheduler & Jobs](./epic5/5.5-scheduler-jobs.md)
|
||||||
- [5.6 Secret Store](./epic5/5.6-secret-store.md)
|
- [5.6 Secret Store](./epic5/5.6-secret-store.md)
|
||||||
- [5.7 Advanced gRPC Features](./epic5/5.7-grpc-services.md) - Streaming, gRPC-Gateway (basic gRPC in Epic 1-2)
|
- [5.7 gRPC Service Definitions and Clients](./epic5/5.7-grpc-services.md)
|
||||||
- [Epic 5 Overview](./epic5/README.md)
|
- [Epic 5 Overview](./epic5/README.md)
|
||||||
|
|
||||||
## Epic 6: Observability & Production Readiness
|
## Epic 6: Observability & Production Readiness
|
||||||
@@ -76,16 +75,13 @@ Tasks are organized by epic and section. Each task file follows the naming conve
|
|||||||
## Epic 8: Advanced Features & Polish
|
## Epic 8: Advanced Features & Polish
|
||||||
- [8.1 OIDC Support](./epic8/8.1-oidc-support.md)
|
- [8.1 OIDC Support](./epic8/8.1-oidc-support.md)
|
||||||
- [8.2 GraphQL API](./epic8/8.2-graphql-api.md)
|
- [8.2 GraphQL API](./epic8/8.2-graphql-api.md)
|
||||||
- [8.3 Additional Sample Feature Services](./epic8/8.3-additional-modules.md) - Notification & Analytics Services
|
- [8.3 Additional Modules](./epic8/8.3-additional-modules.md)
|
||||||
- [8.4 Final Polish](./epic8/8.4-final-polish.md)
|
- [8.4 Final Polish](./epic8/8.4-final-polish.md)
|
||||||
- [Epic 8 Overview](./epic8/README.md)
|
- [Epic 8 Overview](./epic8/README.md)
|
||||||
|
|
||||||
**Note:** API Gateway is now in Epic 1 (Story 1.8) as core infrastructure, not an advanced feature.
|
|
||||||
|
|
||||||
## Task Status Tracking
|
## Task Status Tracking
|
||||||
|
|
||||||
To track task completion:
|
To track task completion:
|
||||||
|
|
||||||
1. Update the Status field in each task file
|
1. Update the Status field in each task file
|
||||||
2. Update checkboxes in the main plan.md
|
2. Update checkboxes in the main plan.md
|
||||||
3. Reference task IDs in commit messages: `[0.1.1] Initialize Go module`
|
3. Reference task IDs in commit messages: `[0.1.1] Initialize Go module`
|
||||||
|
|||||||
@@ -12,14 +12,14 @@ Tasks are organized by epic, with each major task section having its own detaile
|
|||||||
### Epic 1: Core Kernel & Infrastructure
|
### Epic 1: Core Kernel & Infrastructure
|
||||||
- [Epic 1 Tasks](./epic1/README.md) - All Epic 1 tasks
|
- [Epic 1 Tasks](./epic1/README.md) - All Epic 1 tasks
|
||||||
|
|
||||||
### Epic 2: Core Services (Authentication & Authorization)
|
### Epic 2: Authentication & Authorization
|
||||||
- [Epic 2 Tasks](./epic2/README.md) - Auth, Identity, Authz, Audit as independent services
|
- [Epic 2 Tasks](./epic2/README.md) - All Epic 2 tasks
|
||||||
|
|
||||||
### Epic 3: Module Framework (Feature Services)
|
### Epic 3: Module Framework
|
||||||
- [Epic 3 Tasks](./epic3/README.md) - Module framework for feature services
|
- [Epic 3 Tasks](./epic3/README.md) - All Epic 3 tasks
|
||||||
|
|
||||||
### Epic 4: Sample Feature Service (Blog Service)
|
### Epic 4: Sample Feature Module (Blog)
|
||||||
- [Epic 4 Tasks](./epic4/README.md) - Blog Service as reference implementation
|
- [Epic 4 Tasks](./epic4/README.md) - All Epic 4 tasks
|
||||||
|
|
||||||
### Epic 5: Infrastructure Adapters
|
### Epic 5: Infrastructure Adapters
|
||||||
- [Epic 5 Tasks](./epic5/README.md) - All Epic 5 tasks
|
- [Epic 5 Tasks](./epic5/README.md) - All Epic 5 tasks
|
||||||
@@ -36,7 +36,6 @@ Tasks are organized by epic, with each major task section having its own detaile
|
|||||||
## Task Status
|
## Task Status
|
||||||
|
|
||||||
Each task file includes:
|
Each task file includes:
|
||||||
|
|
||||||
- **Task ID**: Unique identifier (e.g., `0.1.1`)
|
- **Task ID**: Unique identifier (e.g., `0.1.1`)
|
||||||
- **Title**: Descriptive task name
|
- **Title**: Descriptive task name
|
||||||
- **Epic**: Implementation epic
|
- **Epic**: Implementation epic
|
||||||
@@ -51,7 +50,6 @@ Each task file includes:
|
|||||||
## Task Tracking
|
## Task Tracking
|
||||||
|
|
||||||
Tasks can be tracked using:
|
Tasks can be tracked using:
|
||||||
|
|
||||||
- GitHub Issues (linked from tasks)
|
- GitHub Issues (linked from tasks)
|
||||||
- Project boards
|
- Project boards
|
||||||
- Task management tools
|
- Task management tools
|
||||||
|
|||||||
@@ -19,7 +19,6 @@ This story implements a complete logging system using Zap that provides structur
|
|||||||
|
|
||||||
### 1. Logger Interface (`pkg/logger/logger.go`)
|
### 1. Logger Interface (`pkg/logger/logger.go`)
|
||||||
Define `Logger` interface with:
|
Define `Logger` interface with:
|
||||||
|
|
||||||
- `Debug(msg string, fields ...Field)` - Debug level logging
|
- `Debug(msg string, fields ...Field)` - Debug level logging
|
||||||
- `Info(msg string, fields ...Field)` - Info level logging
|
- `Info(msg string, fields ...Field)` - Info level logging
|
||||||
- `Warn(msg string, fields ...Field)` - Warning level logging
|
- `Warn(msg string, fields ...Field)` - Warning level logging
|
||||||
@@ -31,7 +30,6 @@ Define `Logger` interface with:
|
|||||||
|
|
||||||
### 2. Zap Implementation (`internal/logger/zap_logger.go`)
|
### 2. Zap Implementation (`internal/logger/zap_logger.go`)
|
||||||
Implement `Logger` interface using Zap:
|
Implement `Logger` interface using Zap:
|
||||||
|
|
||||||
- Structured JSON logging for production mode
|
- Structured JSON logging for production mode
|
||||||
- Human-readable console logging for development mode
|
- Human-readable console logging for development mode
|
||||||
- Configurable log levels (debug, info, warn, error)
|
- Configurable log levels (debug, info, warn, error)
|
||||||
@@ -42,7 +40,6 @@ Implement `Logger` interface using Zap:
|
|||||||
|
|
||||||
### 3. Request ID Middleware (`internal/logger/middleware.go`)
|
### 3. Request ID Middleware (`internal/logger/middleware.go`)
|
||||||
Gin middleware for request correlation:
|
Gin middleware for request correlation:
|
||||||
|
|
||||||
- Generate unique request ID per HTTP request
|
- Generate unique request ID per HTTP request
|
||||||
- Add request ID to request context
|
- Add request ID to request context
|
||||||
- Add request ID to all logs within request context
|
- Add request ID to all logs within request context
|
||||||
|
|||||||
@@ -19,7 +19,6 @@ This story sets up the complete CI/CD pipeline using GitHub Actions and provides
|
|||||||
|
|
||||||
### 1. GitHub Actions Workflow (`.github/workflows/ci.yml`)
|
### 1. GitHub Actions Workflow (`.github/workflows/ci.yml`)
|
||||||
Complete CI pipeline with:
|
Complete CI pipeline with:
|
||||||
|
|
||||||
- Go 1.24 setup
|
- Go 1.24 setup
|
||||||
- Go module caching for faster builds
|
- Go module caching for faster builds
|
||||||
- Linting with golangci-lint or staticcheck
|
- Linting with golangci-lint or staticcheck
|
||||||
@@ -31,7 +30,6 @@ Complete CI pipeline with:
|
|||||||
|
|
||||||
### 2. Comprehensive Makefile
|
### 2. Comprehensive Makefile
|
||||||
Developer-friendly Makefile with commands:
|
Developer-friendly Makefile with commands:
|
||||||
|
|
||||||
- `make test` - Run all tests
|
- `make test` - Run all tests
|
||||||
- `make test-coverage` - Run tests with coverage report
|
- `make test-coverage` - Run tests with coverage report
|
||||||
- `make lint` - Run linters
|
- `make lint` - Run linters
|
||||||
|
|||||||
@@ -19,7 +19,6 @@ This story implements the dependency injection system using Uber FX and creates
|
|||||||
|
|
||||||
### 1. DI Container (`internal/di/container.go`)
|
### 1. DI Container (`internal/di/container.go`)
|
||||||
FX-based dependency injection container:
|
FX-based dependency injection container:
|
||||||
|
|
||||||
- Initialize FX container
|
- Initialize FX container
|
||||||
- Register Config and Logger providers
|
- Register Config and Logger providers
|
||||||
- Basic lifecycle hooks (OnStart, OnStop)
|
- Basic lifecycle hooks (OnStart, OnStop)
|
||||||
@@ -28,14 +27,12 @@ FX-based dependency injection container:
|
|||||||
|
|
||||||
### 2. DI Providers (`internal/di/providers.go`)
|
### 2. DI Providers (`internal/di/providers.go`)
|
||||||
Provider functions for core services:
|
Provider functions for core services:
|
||||||
|
|
||||||
- `ProvideConfig() fx.Option` - Configuration provider
|
- `ProvideConfig() fx.Option` - Configuration provider
|
||||||
- `ProvideLogger() fx.Option` - Logger provider
|
- `ProvideLogger() fx.Option` - Logger provider
|
||||||
- Provider functions return FX options for easy composition
|
- Provider functions return FX options for easy composition
|
||||||
|
|
||||||
### 3. Application Entry Point (`cmd/platform/main.go`)
|
### 3. Application Entry Point (`cmd/platform/main.go`)
|
||||||
Main application bootstrap:
|
Main application bootstrap:
|
||||||
|
|
||||||
- Load configuration
|
- Load configuration
|
||||||
- Initialize DI container with core services
|
- Initialize DI container with core services
|
||||||
- Set up basic application lifecycle
|
- Set up basic application lifecycle
|
||||||
|
|||||||
@@ -10,12 +10,10 @@
|
|||||||
- **Dependencies**: 0.5
|
- **Dependencies**: 0.5
|
||||||
|
|
||||||
## Goal
|
## Goal
|
||||||
Extend the DI container to provide core kernel infrastructure services only (no business logic services) with proper lifecycle management, dependency resolution, and service override support.
|
Extend the DI container to provide all core infrastructure services with proper lifecycle management, dependency resolution, and service override support.
|
||||||
|
|
||||||
## Description
|
## Description
|
||||||
This story extends the basic DI container to support core kernel services only: config, logger, health checks, metrics, error bus, observability, and service registry. The container must handle service initialization order, lifecycle management, and provide a clean way to override services for testing.
|
This story extends the basic DI container to support all core services including database, health checks, metrics, and error bus. The container must handle service initialization order, lifecycle management, and provide a clean way to override services for testing.
|
||||||
|
|
||||||
**Note:** Business services (Auth, Identity, Authz, Audit) are NOT in the core kernel. They are separate services implemented in Epic 2.
|
|
||||||
|
|
||||||
## Deliverables
|
## Deliverables
|
||||||
|
|
||||||
@@ -27,17 +25,13 @@ This story extends the basic DI container to support core kernel services only:
|
|||||||
- Error handling during initialization
|
- Error handling during initialization
|
||||||
|
|
||||||
### 2. Provider Functions (`internal/di/providers.go`)
|
### 2. Provider Functions (`internal/di/providers.go`)
|
||||||
Complete provider functions for core kernel services only:
|
Complete provider functions for all core services:
|
||||||
|
|
||||||
- `ProvideConfig() fx.Option` - Configuration provider
|
- `ProvideConfig() fx.Option` - Configuration provider
|
||||||
- `ProvideLogger() fx.Option` - Logger provider
|
- `ProvideLogger() fx.Option` - Logger provider
|
||||||
|
- `ProvideDatabase() fx.Option` - Ent database client provider
|
||||||
- `ProvideHealthCheckers() fx.Option` - Health check registry provider
|
- `ProvideHealthCheckers() fx.Option` - Health check registry provider
|
||||||
- `ProvideMetrics() fx.Option` - Prometheus metrics registry provider
|
- `ProvideMetrics() fx.Option` - Prometheus metrics registry provider
|
||||||
- `ProvideErrorBus() fx.Option` - Error bus provider
|
- `ProvideErrorBus() fx.Option` - Error bus provider
|
||||||
- `ProvideTracer() fx.Option` - OpenTelemetry tracer provider
|
|
||||||
- `ProvideServiceRegistry() fx.Option` - Service registry provider (Consul)
|
|
||||||
|
|
||||||
**Note:** Database provider is NOT in core kernel - each service will create its own database client.
|
|
||||||
|
|
||||||
### 3. Core Module (`internal/di/core_module.go`)
|
### 3. Core Module (`internal/di/core_module.go`)
|
||||||
- Export `CoreModule() fx.Option` that provides all core services
|
- Export `CoreModule() fx.Option` that provides all core services
|
||||||
@@ -67,15 +61,13 @@ Complete provider functions for core kernel services only:
|
|||||||
- Test lifecycle hooks
|
- Test lifecycle hooks
|
||||||
|
|
||||||
## Acceptance Criteria
|
## Acceptance Criteria
|
||||||
- [x] All core kernel services are provided via DI container
|
- [x] All core services are provided via DI container
|
||||||
- [x] Services are initialized in correct dependency order
|
- [x] Services are initialized in correct dependency order
|
||||||
- [x] Lifecycle hooks work for all services
|
- [x] Lifecycle hooks work for all services
|
||||||
- [x] Services can be overridden for testing
|
- [x] Services can be overridden for testing
|
||||||
- [x] DI container compiles without errors
|
- [x] DI container compiles without errors
|
||||||
- [x] CoreModule can be imported and used
|
- [x] CoreModule can be imported and used
|
||||||
- [x] Error handling works during initialization
|
- [x] Error handling works during initialization
|
||||||
- [x] No business logic services in core kernel
|
|
||||||
- [x] Service registry provider is included
|
|
||||||
|
|
||||||
## Related ADRs
|
## Related ADRs
|
||||||
- [ADR-0003: Dependency Injection Framework](../../adr/0003-dependency-injection-framework.md)
|
- [ADR-0003: Dependency Injection Framework](../../adr/0003-dependency-injection-framework.md)
|
||||||
|
|||||||
@@ -10,80 +10,103 @@
|
|||||||
- **Dependencies**: 1.1
|
- **Dependencies**: 1.1
|
||||||
|
|
||||||
## Goal
|
## Goal
|
||||||
Set up database client foundation for services. Each service will have its own database connection pool and schema.
|
Set up a complete database layer using Ent ORM with core domain entities, migrations, and connection management.
|
||||||
|
|
||||||
## Description
|
## Description
|
||||||
This story implements the database client foundation that services will use. It includes connection management, schema isolation support, connection pooling configuration, and migration runner wrapper. Core domain entities (User, Role, Permission, AuditLog) are NOT implemented here - they are part of their respective services in Epic 2.
|
This story implements the complete database layer using Ent ORM. It includes defining core domain entities (User, Role, Permission, AuditLog), setting up migrations, configuring connection pooling, and creating a database client that integrates with the DI container.
|
||||||
|
|
||||||
## Deliverables
|
## Deliverables
|
||||||
|
|
||||||
### 1. Database Client Foundation
|
### 1. Ent Schema Initialization
|
||||||
- Database client wrapper in `internal/infra/database/client.go`
|
- Initialize Ent schema in `internal/ent/`
|
||||||
- Support for schema isolation (each service uses its own schema)
|
- Set up code generation
|
||||||
- Connection pooling configuration per service
|
|
||||||
- Migration runner wrapper
|
|
||||||
- Database health check integration
|
|
||||||
|
|
||||||
### 2. Database Client Functions
|
### 2. Core Domain Entities (`internal/ent/schema/`)
|
||||||
- `NewEntClient(dsn string, schema string) (*ent.Client, error)` - supports schema isolation
|
Define core entities:
|
||||||
|
- **User** (`user.go`): ID, email, password_hash, verified, created_at, updated_at
|
||||||
|
- **Role** (`role.go`): ID, name, description, created_at
|
||||||
|
- **Permission** (`permission.go`): ID, name (format: "module.resource.action")
|
||||||
|
- **AuditLog** (`audit_log.go`): ID, actor_id, action, target_id, metadata (JSON), timestamp
|
||||||
|
- **Relationships**:
|
||||||
|
- `role_permissions.go` - Many-to-many between Role and Permission
|
||||||
|
- `user_roles.go` - Many-to-many between User and Role
|
||||||
|
|
||||||
|
### 3. Generated Ent Code
|
||||||
|
- Run `go generate ./internal/ent`
|
||||||
|
- Verify generated code compiles
|
||||||
|
- Type-safe database operations
|
||||||
|
|
||||||
|
### 4. Database Client (`internal/infra/database/client.go`)
|
||||||
|
- `NewEntClient(dsn string) (*ent.Client, error)` function
|
||||||
- Connection pooling configuration:
|
- Connection pooling configuration:
|
||||||
- Max connections per service
|
- Max connections
|
||||||
- Max idle connections per service
|
- Max idle connections
|
||||||
- Connection lifetime
|
- Connection lifetime
|
||||||
- Idle timeout
|
- Idle timeout
|
||||||
- Per-service connection pool management
|
|
||||||
- Migration runner wrapper
|
- Migration runner wrapper
|
||||||
- Database health check integration
|
- Database health check integration
|
||||||
- Graceful connection closing
|
- Graceful connection closing
|
||||||
|
|
||||||
### 3. Database Configuration
|
### 5. Database Configuration
|
||||||
- Add database config to `config/default.yaml`:
|
- Add database config to `config/default.yaml`:
|
||||||
- Connection string (DSN) - shared PostgreSQL instance
|
- Connection string (DSN)
|
||||||
- Connection pool settings per service
|
- Connection pool settings
|
||||||
- Schema isolation configuration
|
|
||||||
- Migration settings
|
- Migration settings
|
||||||
- Driver configuration
|
- Driver configuration
|
||||||
|
|
||||||
### 4. Database Client Factory
|
### 6. DI Integration
|
||||||
- Factory function for creating service-specific database clients
|
- Provider function for database client
|
||||||
- Each service manages its own connection pool
|
- Register in DI container
|
||||||
- Support for multiple services connecting to same database instance with different schemas
|
- Lifecycle management (close on shutdown)
|
||||||
|
|
||||||
## Implementation Steps
|
## Implementation Steps
|
||||||
|
|
||||||
1. **Create Database Client Wrapper**
|
1. **Install Ent**
|
||||||
- Create `internal/infra/database/client.go`
|
```bash
|
||||||
- Implement `NewEntClient(dsn, schema)` function
|
go get entgo.io/ent/cmd/ent
|
||||||
- Add connection pooling configuration
|
```
|
||||||
- Add schema isolation support
|
|
||||||
|
|
||||||
2. **Add Configuration**
|
2. **Initialize Ent Schema**
|
||||||
|
```bash
|
||||||
|
go run entgo.io/ent/cmd/ent init User Role Permission AuditLog
|
||||||
|
```
|
||||||
|
|
||||||
|
3. **Define Core Entities**
|
||||||
|
- Create schema files for each entity
|
||||||
|
- Define fields and relationships
|
||||||
|
- Add indexes where needed
|
||||||
|
|
||||||
|
4. **Generate Ent Code**
|
||||||
|
```bash
|
||||||
|
go generate ./internal/ent
|
||||||
|
```
|
||||||
|
|
||||||
|
5. **Create Database Client**
|
||||||
|
- Create `internal/infra/database/client.go`
|
||||||
|
- Implement connection management
|
||||||
|
- Add migration runner
|
||||||
|
- Add health check
|
||||||
|
|
||||||
|
6. **Add Configuration**
|
||||||
- Update `config/default.yaml`
|
- Update `config/default.yaml`
|
||||||
- Add database configuration section
|
- Add database configuration section
|
||||||
- Add schema isolation settings
|
|
||||||
|
|
||||||
3. **Create Database Client Factory**
|
7. **Integrate with DI**
|
||||||
- Factory function for service-specific clients
|
- Create provider function
|
||||||
- Support for per-service connection pools
|
- Register in container
|
||||||
- Migration runner wrapper
|
- Test connection
|
||||||
|
|
||||||
4. **Test Database Client**
|
|
||||||
- Test connection with schema isolation
|
|
||||||
- Test multiple services connecting to same database
|
|
||||||
- Test connection pooling
|
|
||||||
|
|
||||||
## Acceptance Criteria
|
## Acceptance Criteria
|
||||||
- [x] Database client connects to PostgreSQL with schema support
|
- [x] Ent schema compiles and generates code successfully
|
||||||
- [x] Connection pooling is configured correctly per service
|
- [x] Database client connects to PostgreSQL
|
||||||
|
- [x] Core entities can be created and queried
|
||||||
|
- [x] Migrations run successfully on startup
|
||||||
|
- [x] Connection pooling is configured correctly
|
||||||
- [x] Database health check works
|
- [x] Database health check works
|
||||||
- [x] Multiple services can connect to same database instance with different schemas
|
- [x] All entities have proper indexes and relationships
|
||||||
- [x] Each service manages its own connection pool
|
- [x] Database client is injectable via DI
|
||||||
- [x] Database client factory works correctly
|
|
||||||
- [x] Schema isolation is supported
|
|
||||||
- [x] Connections are closed gracefully on shutdown
|
- [x] Connections are closed gracefully on shutdown
|
||||||
|
|
||||||
**Note:** Core domain entities (User, Role, Permission, AuditLog) are implemented in Epic 2 as part of their respective services (Identity, Authz, Audit).
|
|
||||||
|
|
||||||
## Related ADRs
|
## Related ADRs
|
||||||
- [ADR-0013: Database ORM](../../adr/0013-database-orm.md)
|
- [ADR-0013: Database ORM](../../adr/0013-database-orm.md)
|
||||||
|
|
||||||
@@ -109,14 +132,13 @@ go run cmd/platform/main.go
|
|||||||
```
|
```
|
||||||
|
|
||||||
## Files to Create/Modify
|
## Files to Create/Modify
|
||||||
- `internal/infra/database/client.go` - Database client wrapper with schema support
|
- `internal/ent/schema/user.go` - User entity
|
||||||
- `internal/infra/database/factory.go` - Database client factory for services
|
- `internal/ent/schema/role.go` - Role entity
|
||||||
- `config/default.yaml` - Add database config with schema isolation settings
|
- `internal/ent/schema/permission.go` - Permission entity
|
||||||
|
- `internal/ent/schema/audit_log.go` - AuditLog entity
|
||||||
**Note:** Entity schemas are created in Epic 2:
|
- `internal/ent/schema/role_permissions.go` - Relationship
|
||||||
|
- `internal/ent/schema/user_roles.go` - Relationship
|
||||||
- `services/identity/ent/schema/user.go` - User entity (Identity Service)
|
- `internal/infra/database/client.go` - Database client
|
||||||
- `services/authz/ent/schema/role.go` - Role entity (Authz Service)
|
- `internal/di/providers.go` - Add database provider
|
||||||
- `services/authz/ent/schema/permission.go` - Permission entity (Authz Service)
|
- `config/default.yaml` - Add database config
|
||||||
- `services/audit/ent/schema/audit_log.go` - AuditLog entity (Audit Service)
|
|
||||||
|
|
||||||
|
|||||||
@@ -10,42 +10,40 @@
|
|||||||
- **Dependencies**: 1.1, 1.3, 1.4
|
- **Dependencies**: 1.1, 1.3, 1.4
|
||||||
|
|
||||||
## Goal
|
## Goal
|
||||||
Create HTTP and gRPC server foundation that services can use. Each service will have its own server instance.
|
Create a production-ready HTTP server with comprehensive middleware for security, observability, and error handling.
|
||||||
|
|
||||||
## Description
|
## Description
|
||||||
This story implements HTTP and gRPC server foundations that services will use to create their own server instances. It includes common middleware, server lifecycle management, and integration with the DI container. Services (Auth, Identity, etc.) will use these foundations in Epic 2.
|
This story implements a complete HTTP server using Gin with a comprehensive middleware stack including request ID generation, structured logging, panic recovery, metrics collection, CORS, and graceful shutdown.
|
||||||
|
|
||||||
## Deliverables
|
## Deliverables
|
||||||
|
|
||||||
### 1. HTTP Server Foundation (`internal/server/http.go`)
|
### 1. HTTP Server (`internal/server/server.go`)
|
||||||
- HTTP server helper functions
|
- Gin router initialization
|
||||||
- Gin router initialization helper
|
|
||||||
- Server configuration (port, host, timeouts)
|
- Server configuration (port, host, timeouts)
|
||||||
- Graceful shutdown handling
|
- Graceful shutdown handling
|
||||||
- Reusable by services
|
|
||||||
|
|
||||||
### 2. gRPC Server Foundation (`internal/server/grpc.go`)
|
### 2. Comprehensive Middleware Stack
|
||||||
- gRPC server initialization helper
|
|
||||||
- Interceptor support (logging, tracing, metrics)
|
|
||||||
- Server lifecycle management
|
|
||||||
- Reusable by services
|
|
||||||
|
|
||||||
### 3. Common Middleware Stack
|
|
||||||
- **Request ID Generator**: Unique ID per request
|
- **Request ID Generator**: Unique ID per request
|
||||||
- **Structured Logging**: Log all requests with context
|
- **Structured Logging**: Log all requests with context
|
||||||
- **Panic Recovery**: Recover panics → error bus
|
- **Panic Recovery**: Recover panics → error bus
|
||||||
- **Prometheus Metrics**: Collect request metrics
|
- **Prometheus Metrics**: Collect request metrics
|
||||||
- **CORS Support**: Configurable CORS headers (for HTTP)
|
- **CORS Support**: Configurable CORS headers
|
||||||
- **Request Timeout**: Handle request timeouts
|
- **Request Timeout**: Handle request timeouts
|
||||||
- **Response Compression**: Gzip compression for responses (HTTP)
|
- **Response Compression**: Gzip compression for responses
|
||||||
|
|
||||||
|
### 3. Core Route Registration
|
||||||
|
- `GET /healthz` - Liveness probe
|
||||||
|
- `GET /ready` - Readiness probe
|
||||||
|
- `GET /metrics` - Prometheus metrics
|
||||||
|
|
||||||
### 4. FX Lifecycle Integration
|
### 4. FX Lifecycle Integration
|
||||||
- Server lifecycle management helpers
|
- HTTP server starts on `OnStart` hook
|
||||||
- Graceful shutdown support
|
- Graceful shutdown on `OnStop` hook (drains connections)
|
||||||
- Port configuration from config system
|
- Port configuration from config system
|
||||||
- Reusable by services
|
|
||||||
|
|
||||||
**Note:** Services will use these foundations to create their own server instances in Epic 2.
|
### 5. Integration
|
||||||
|
- Integration with main application entry point
|
||||||
|
- Integration with all middleware systems
|
||||||
|
|
||||||
## Implementation Steps
|
## Implementation Steps
|
||||||
|
|
||||||
@@ -82,15 +80,15 @@ This story implements HTTP and gRPC server foundations that services will use to
|
|||||||
- Test graceful shutdown
|
- Test graceful shutdown
|
||||||
|
|
||||||
## Acceptance Criteria
|
## Acceptance Criteria
|
||||||
- [x] HTTP server foundation is reusable by services
|
- [x] HTTP server starts successfully
|
||||||
- [x] gRPC server foundation is reusable by services
|
|
||||||
- [x] All middleware executes in correct order
|
- [x] All middleware executes in correct order
|
||||||
- [x] Request IDs are generated and logged
|
- [x] Request IDs are generated and logged
|
||||||
- [x] Metrics are collected for all requests
|
- [x] Metrics are collected for all requests
|
||||||
- [x] Panics are recovered and handled
|
- [x] Panics are recovered and handled
|
||||||
- [x] Graceful shutdown works correctly
|
- [x] Graceful shutdown works correctly
|
||||||
- [x] Servers are configurable via config system
|
- [x] Server is configurable via config system
|
||||||
- [x] Services can create their own server instances using these foundations
|
- [x] CORS is configurable per environment
|
||||||
|
- [x] All core endpoints work correctly
|
||||||
|
|
||||||
## Related ADRs
|
## Related ADRs
|
||||||
- [ADR-0006: HTTP Framework](../../adr/0006-http-framework.md)
|
- [ADR-0006: HTTP Framework](../../adr/0006-http-framework.md)
|
||||||
@@ -117,10 +115,8 @@ curl http://localhost:8080/metrics
|
|||||||
```
|
```
|
||||||
|
|
||||||
## Files to Create/Modify
|
## Files to Create/Modify
|
||||||
- `internal/server/http.go` - HTTP server foundation
|
- `internal/server/server.go` - HTTP server
|
||||||
- `internal/server/grpc.go` - gRPC server foundation
|
- `internal/server/middleware.go` - Middleware functions
|
||||||
- `internal/server/middleware.go` - Common middleware functions
|
- `internal/di/providers.go` - Add server provider
|
||||||
- `config/default.yaml` - Add server configuration
|
- `config/default.yaml` - Add server configuration
|
||||||
|
|
||||||
**Note:** Services will create their own server instances using these foundations in Epic 2.
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,148 +0,0 @@
|
|||||||
# Story 1.7: Service Client Interfaces
|
|
||||||
|
|
||||||
## Metadata
|
|
||||||
- **Story ID**: 1.7
|
|
||||||
- **Title**: Service Client Interfaces
|
|
||||||
- **Epic**: 1 - Core Kernel & Infrastructure
|
|
||||||
- **Status**: In Progress
|
|
||||||
- **Priority**: High
|
|
||||||
- **Estimated Time**: 4-6 hours
|
|
||||||
- **Dependencies**: 1.1
|
|
||||||
|
|
||||||
## Goal
|
|
||||||
Create service client interfaces for all core services to enable microservices communication. All inter-service communication will go through these interfaces.
|
|
||||||
|
|
||||||
## Description
|
|
||||||
This story defines service client interfaces for all core services (Auth, Identity, Authz, Audit) and creates a service client factory that can create gRPC (primary) or HTTP (fallback) clients. Service clients use Consul for service discovery.
|
|
||||||
|
|
||||||
## Deliverables
|
|
||||||
|
|
||||||
### 1. Service Client Interfaces (`pkg/services/`)
|
|
||||||
Define interfaces for all core services:
|
|
||||||
|
|
||||||
- `AuthServiceClient` in `pkg/services/auth.go`:
|
|
||||||
- `Login(ctx, email, password) (*TokenResponse, error)`
|
|
||||||
- `RefreshToken(ctx, refreshToken) (*TokenResponse, error)`
|
|
||||||
- `ValidateToken(ctx, token) (*TokenClaims, error)`
|
|
||||||
|
|
||||||
- `IdentityServiceClient` in `pkg/services/identity.go`:
|
|
||||||
- `GetUser(ctx, id) (*User, error)`
|
|
||||||
- `GetUserByEmail(ctx, email) (*User, error)`
|
|
||||||
- `CreateUser(ctx, user) (*User, error)`
|
|
||||||
- `UpdateUser(ctx, id, user) (*User, error)`
|
|
||||||
- `DeleteUser(ctx, id) error`
|
|
||||||
- `VerifyEmail(ctx, token) error`
|
|
||||||
- `RequestPasswordReset(ctx, email) error`
|
|
||||||
- `ResetPassword(ctx, token, newPassword) error`
|
|
||||||
|
|
||||||
- `AuthzServiceClient` in `pkg/services/authz.go`:
|
|
||||||
- `Authorize(ctx, userID, permission) error`
|
|
||||||
- `HasPermission(ctx, userID, permission) (bool, error)`
|
|
||||||
- `GetUserPermissions(ctx, userID) ([]Permission, error)`
|
|
||||||
|
|
||||||
- `AuditServiceClient` in `pkg/services/audit.go`:
|
|
||||||
- `Record(ctx, action) error`
|
|
||||||
- `Query(ctx, filters) ([]AuditLog, error)`
|
|
||||||
|
|
||||||
### 2. Service Client Factory (`internal/services/factory.go`)
|
|
||||||
- `NewServiceClient(serviceName string, registry ServiceRegistry) (ServiceClient, error)`
|
|
||||||
- Support for gRPC clients (primary)
|
|
||||||
- Support for HTTP clients (fallback)
|
|
||||||
- Service discovery integration via Consul
|
|
||||||
- Connection pooling and lifecycle management
|
|
||||||
|
|
||||||
### 3. gRPC Client Implementation (`internal/services/grpc/client/`)
|
|
||||||
- gRPC client implementations for each service
|
|
||||||
- Service discovery integration
|
|
||||||
- Connection management
|
|
||||||
- Retry and circuit breaker support
|
|
||||||
|
|
||||||
### 4. HTTP Client Implementation (`internal/services/http/client/`)
|
|
||||||
- HTTP client implementations for each service (fallback)
|
|
||||||
- Service discovery integration
|
|
||||||
- Request/response handling
|
|
||||||
- Retry support
|
|
||||||
|
|
||||||
### 5. Configuration
|
|
||||||
- Service client configuration in `config/default.yaml`:
|
|
||||||
- Protocol selection (gRPC/HTTP)
|
|
||||||
- Service discovery settings
|
|
||||||
- Connection pool settings
|
|
||||||
- Retry and timeout configuration
|
|
||||||
|
|
||||||
## Implementation Steps
|
|
||||||
|
|
||||||
1. **Define Service Client Interfaces**
|
|
||||||
- Create `pkg/services/auth.go`
|
|
||||||
- Create `pkg/services/identity.go`
|
|
||||||
- Create `pkg/services/authz.go`
|
|
||||||
- Create `pkg/services/audit.go`
|
|
||||||
|
|
||||||
2. **Create Service Client Factory**
|
|
||||||
- Create `internal/services/factory.go`
|
|
||||||
- Implement client creation logic
|
|
||||||
- Integrate with service registry (Consul)
|
|
||||||
|
|
||||||
3. **Implement gRPC Clients**
|
|
||||||
- Create `internal/services/grpc/client/`
|
|
||||||
- Implement clients for each service
|
|
||||||
- Add service discovery integration
|
|
||||||
|
|
||||||
4. **Implement HTTP Clients (Fallback)**
|
|
||||||
- Create `internal/services/http/client/`
|
|
||||||
- Implement clients for each service
|
|
||||||
- Add service discovery integration
|
|
||||||
|
|
||||||
5. **Add Configuration**
|
|
||||||
- Update `config/default.yaml`
|
|
||||||
- Add service client configuration
|
|
||||||
|
|
||||||
6. **Test Service Clients**
|
|
||||||
- Test client creation
|
|
||||||
- Test service discovery
|
|
||||||
- Test gRPC and HTTP clients
|
|
||||||
|
|
||||||
## Acceptance Criteria
|
|
||||||
- [x] Service client interfaces are defined for all core services
|
|
||||||
- [x] Service factory creates gRPC clients
|
|
||||||
- [x] Service factory creates HTTP clients (fallback)
|
|
||||||
- [x] Service clients use Consul for service discovery
|
|
||||||
- [x] Service clients are injectable via DI
|
|
||||||
- [x] Configuration supports protocol selection
|
|
||||||
- [x] All inter-service communication goes through service clients
|
|
||||||
- [x] Service clients handle connection pooling and lifecycle
|
|
||||||
|
|
||||||
## Related ADRs
|
|
||||||
- [ADR-0030: Service Communication Strategy](../../adr/0030-service-communication-strategy.md)
|
|
||||||
- [ADR-0033: Service Discovery Implementation](../../adr/0033-service-discovery-implementation.md)
|
|
||||||
|
|
||||||
## Implementation Notes
|
|
||||||
- gRPC is the primary protocol, HTTP is fallback
|
|
||||||
- All clients use Consul for service discovery
|
|
||||||
- Service clients should handle retries and circuit breakers
|
|
||||||
- Connection pooling is important for performance
|
|
||||||
- Service clients should be stateless and thread-safe
|
|
||||||
|
|
||||||
## Testing
|
|
||||||
```bash
|
|
||||||
# Test service client interfaces
|
|
||||||
go test ./pkg/services/...
|
|
||||||
|
|
||||||
# Test service client factory
|
|
||||||
go test ./internal/services/...
|
|
||||||
|
|
||||||
# Test with Consul
|
|
||||||
docker-compose up consul
|
|
||||||
go test ./internal/services/... -tags=integration
|
|
||||||
```
|
|
||||||
|
|
||||||
## Files to Create/Modify
|
|
||||||
- `pkg/services/auth.go` - AuthServiceClient interface
|
|
||||||
- `pkg/services/identity.go` - IdentityServiceClient interface
|
|
||||||
- `pkg/services/authz.go` - AuthzServiceClient interface
|
|
||||||
- `pkg/services/audit.go` - AuditServiceClient interface
|
|
||||||
- `internal/services/factory.go` - Service client factory
|
|
||||||
- `internal/services/grpc/client/` - gRPC client implementations
|
|
||||||
- `internal/services/http/client/` - HTTP client implementations
|
|
||||||
- `config/default.yaml` - Add service client configuration
|
|
||||||
|
|
||||||
@@ -1,183 +0,0 @@
|
|||||||
# Story 1.8: API Gateway Implementation
|
|
||||||
|
|
||||||
## Metadata
|
|
||||||
- **Story ID**: 1.8
|
|
||||||
- **Title**: API Gateway Implementation
|
|
||||||
- **Epic**: 1 - Core Kernel & Infrastructure
|
|
||||||
- **Status**: In Progress
|
|
||||||
- **Priority**: High
|
|
||||||
- **Estimated Time**: 8-10 hours
|
|
||||||
- **Dependencies**: 1.1, 1.5, 1.7
|
|
||||||
|
|
||||||
## Goal
|
|
||||||
Implement API Gateway as core infrastructure component that routes all external traffic to backend services via service discovery (Consul). Gateway handles authentication, rate limiting, CORS, and request transformation.
|
|
||||||
|
|
||||||
## Description
|
|
||||||
This story implements the API Gateway service that serves as the single entry point for all external traffic. The gateway routes requests to backend services via Consul service discovery, validates JWT tokens via Auth Service, checks permissions via Authz Service, and handles rate limiting and CORS.
|
|
||||||
|
|
||||||
## Deliverables
|
|
||||||
|
|
||||||
### 1. API Gateway Service Entry Point (`cmd/api-gateway/main.go`)
|
|
||||||
- Service entry point for API Gateway
|
|
||||||
- Bootstrap with core kernel services
|
|
||||||
- Register with Consul service registry
|
|
||||||
- Start HTTP server
|
|
||||||
|
|
||||||
### 2. Gateway Implementation (`services/gateway/internal/`)
|
|
||||||
- **Routing Engine** (`router.go`):
|
|
||||||
- Route configuration from YAML
|
|
||||||
- Path matching and service routing
|
|
||||||
- Service discovery integration (Consul)
|
|
||||||
- Load balancing across service instances
|
|
||||||
|
|
||||||
- **Authentication Middleware** (`auth.go`):
|
|
||||||
- JWT token extraction from headers
|
|
||||||
- Token validation via Auth Service client
|
|
||||||
- User context injection
|
|
||||||
|
|
||||||
- **Authorization Middleware** (`authz.go`):
|
|
||||||
- Permission checking via Authz Service client (optional, for route-level auth)
|
|
||||||
- Route-based permission configuration
|
|
||||||
|
|
||||||
- **Rate Limiting** (`ratelimit.go`):
|
|
||||||
- Per-user rate limiting (via user ID from JWT)
|
|
||||||
- Per-IP rate limiting
|
|
||||||
- Redis-backed rate limiting state
|
|
||||||
- Configurable limits per route
|
|
||||||
|
|
||||||
- **CORS Support** (`cors.go`):
|
|
||||||
- Configurable CORS headers
|
|
||||||
- Preflight request handling
|
|
||||||
|
|
||||||
- **Request/Response Transformation** (`transform.go`):
|
|
||||||
- Request modification before forwarding
|
|
||||||
- Response modification before returning
|
|
||||||
- Header manipulation
|
|
||||||
|
|
||||||
### 3. Gateway Configuration (`config/default.yaml`)
|
|
||||||
```yaml
|
|
||||||
gateway:
|
|
||||||
port: 8080
|
|
||||||
routes:
|
|
||||||
- path: /api/v1/auth/**
|
|
||||||
service: auth-service
|
|
||||||
auth_required: false
|
|
||||||
rate_limit:
|
|
||||||
per_user: 100/minute
|
|
||||||
per_ip: 1000/minute
|
|
||||||
- path: /api/v1/users/**
|
|
||||||
service: identity-service
|
|
||||||
auth_required: true
|
|
||||||
permission: user.read
|
|
||||||
rate_limit:
|
|
||||||
per_user: 50/minute
|
|
||||||
- path: /api/v1/blog/**
|
|
||||||
service: blog-service
|
|
||||||
auth_required: true
|
|
||||||
permission: blog.post.read
|
|
||||||
cors:
|
|
||||||
allowed_origins: ["*"]
|
|
||||||
allowed_methods: ["GET", "POST", "PUT", "DELETE"]
|
|
||||||
allowed_headers: ["Authorization", "Content-Type"]
|
|
||||||
service_discovery:
|
|
||||||
type: consul
|
|
||||||
consul:
|
|
||||||
address: "localhost:8500"
|
|
||||||
```
|
|
||||||
|
|
||||||
### 4. Service Discovery Integration
|
|
||||||
- Consul integration for service discovery
|
|
||||||
- Dynamic service endpoint resolution
|
|
||||||
- Health check filtering (only route to healthy services)
|
|
||||||
- Load balancing across service instances
|
|
||||||
|
|
||||||
### 5. Health Check Endpoint
|
|
||||||
- `GET /healthz` - Gateway health check
|
|
||||||
- `GET /ready` - Gateway readiness (checks service registry connectivity)
|
|
||||||
|
|
||||||
## Implementation Steps
|
|
||||||
|
|
||||||
1. **Create Service Entry Point**
|
|
||||||
- Create `cmd/api-gateway/main.go`
|
|
||||||
- Bootstrap with core kernel
|
|
||||||
- Register with Consul
|
|
||||||
|
|
||||||
2. **Implement Routing Engine**
|
|
||||||
- Create `services/gateway/internal/router.go`
|
|
||||||
- Implement route matching
|
|
||||||
- Integrate with Consul service discovery
|
|
||||||
- Implement load balancing
|
|
||||||
|
|
||||||
3. **Implement Authentication**
|
|
||||||
- Create `services/gateway/internal/auth.go`
|
|
||||||
- JWT token extraction
|
|
||||||
- Token validation via Auth Service client
|
|
||||||
- User context injection
|
|
||||||
|
|
||||||
4. **Implement Rate Limiting**
|
|
||||||
- Create `services/gateway/internal/ratelimit.go`
|
|
||||||
- Redis integration
|
|
||||||
- Per-user and per-IP limiting
|
|
||||||
|
|
||||||
5. **Implement CORS**
|
|
||||||
- Create `services/gateway/internal/cors.go`
|
|
||||||
- Configurable CORS support
|
|
||||||
|
|
||||||
6. **Add Configuration**
|
|
||||||
- Update `config/default.yaml`
|
|
||||||
- Add gateway configuration
|
|
||||||
|
|
||||||
7. **Test Gateway**
|
|
||||||
- Test routing to backend services
|
|
||||||
- Test authentication
|
|
||||||
- Test rate limiting
|
|
||||||
- Test service discovery
|
|
||||||
|
|
||||||
## Acceptance Criteria
|
|
||||||
- [x] API Gateway service is independently deployable
|
|
||||||
- [x] Gateway routes requests to backend services correctly
|
|
||||||
- [x] JWT validation works via Auth Service client
|
|
||||||
- [x] Rate limiting works correctly (per-user and per-IP)
|
|
||||||
- [x] CORS is configurable and works
|
|
||||||
- [x] Service discovery integration works (Consul)
|
|
||||||
- [x] Gateway has health check endpoint
|
|
||||||
- [x] All external traffic goes through gateway
|
|
||||||
- [x] Gateway registers with Consul
|
|
||||||
- [x] Load balancing works across service instances
|
|
||||||
|
|
||||||
## Related ADRs
|
|
||||||
- [ADR-0032: API Gateway Strategy](../../adr/0032-api-gateway-strategy.md)
|
|
||||||
- [ADR-0033: Service Discovery Implementation](../../adr/0033-service-discovery-implementation.md)
|
|
||||||
- [ADR-0030: Service Communication Strategy](../../adr/0030-service-communication-strategy.md)
|
|
||||||
|
|
||||||
## Implementation Notes
|
|
||||||
- Gateway is a core infrastructure component, not optional
|
|
||||||
- All external traffic must go through gateway
|
|
||||||
- Gateway uses service clients for backend communication
|
|
||||||
- Service discovery via Consul is required
|
|
||||||
- Rate limiting state is stored in Redis
|
|
||||||
- Gateway should be horizontally scalable
|
|
||||||
|
|
||||||
## Testing
|
|
||||||
```bash
|
|
||||||
# Test gateway startup
|
|
||||||
go run cmd/api-gateway/main.go
|
|
||||||
|
|
||||||
# Test routing
|
|
||||||
curl http://localhost:8080/api/v1/auth/login
|
|
||||||
|
|
||||||
# Test with Consul
|
|
||||||
docker-compose up consul
|
|
||||||
go test ./services/gateway/... -tags=integration
|
|
||||||
```
|
|
||||||
|
|
||||||
## Files to Create/Modify
|
|
||||||
- `cmd/api-gateway/main.go` - Gateway service entry point
|
|
||||||
- `services/gateway/internal/router.go` - Routing engine
|
|
||||||
- `services/gateway/internal/auth.go` - Authentication middleware
|
|
||||||
- `services/gateway/internal/authz.go` - Authorization middleware
|
|
||||||
- `services/gateway/internal/ratelimit.go` - Rate limiting
|
|
||||||
- `services/gateway/internal/cors.go` - CORS support
|
|
||||||
- `services/gateway/internal/transform.go` - Request/response transformation
|
|
||||||
- `config/default.yaml` - Add gateway configuration
|
|
||||||
|
|
||||||
@@ -1,23 +1,19 @@
|
|||||||
# Epic 1: Core Kernel & Infrastructure
|
# Epic 1: Core Kernel & Infrastructure
|
||||||
|
|
||||||
## Overview
|
## Overview
|
||||||
Build the core kernel infrastructure (config, logger, DI, health, metrics, observability) with **no business logic**. Implement API Gateway as core infrastructure component. Create service client interfaces and service registry foundation. Establish HTTP/gRPC server foundations that services will use.
|
Extend DI container to support all core services, implement database layer with Ent ORM, build health monitoring and metrics system, create error handling and error bus, establish HTTP server with comprehensive middleware stack, and integrate OpenTelemetry for distributed tracing.
|
||||||
|
|
||||||
**Note:** This epic focuses on infrastructure only. Business services (Auth, Identity, Authz, Audit) are implemented in Epic 2 as separate microservices.
|
|
||||||
|
|
||||||
## Stories
|
## Stories
|
||||||
|
|
||||||
### 1.1 Enhanced Dependency Injection Container
|
### 1.1 Enhanced Dependency Injection Container
|
||||||
- [Story: 1.1 - Enhanced DI Container](./1.1-enhanced-di-container.md)
|
- [Story: 1.1 - Enhanced DI Container](./1.1-enhanced-di-container.md)
|
||||||
- **Goal:** Extend the DI container to provide core kernel infrastructure services only (no business logic) with proper lifecycle management.
|
- **Goal:** Extend the DI container to provide all core infrastructure services with proper lifecycle management.
|
||||||
- **Deliverables:** Extended DI container, provider functions for core kernel services only, core module export
|
- **Deliverables:** Extended DI container, provider functions for all services, core module export
|
||||||
|
|
||||||
### 1.2 Database Client Foundation
|
### 1.2 Database Layer with Ent ORM
|
||||||
- [Story: 1.2 - Database Client Foundation](./1.2-database-layer.md)
|
- [Story: 1.2 - Database Layer](./1.2-database-layer.md)
|
||||||
- **Goal:** Set up database client foundation for services. Each service will have its own database connection and schema.
|
- **Goal:** Set up a complete database layer using Ent ORM with core domain entities, migrations, and connection management.
|
||||||
- **Deliverables:** Database client wrapper with schema support, connection pooling, per-service connection management
|
- **Deliverables:** Ent schema, core entities, database client, migrations, connection pooling
|
||||||
|
|
||||||
**Note:** Core domain entities (User, Role, Permission, AuditLog) are implemented in Epic 2 as part of their respective services.
|
|
||||||
|
|
||||||
### 1.3 Health Monitoring and Metrics System
|
### 1.3 Health Monitoring and Metrics System
|
||||||
- [Story: 1.3 - Health & Metrics](./1.3-health-metrics-system.md)
|
- [Story: 1.3 - Health & Metrics](./1.3-health-metrics-system.md)
|
||||||
@@ -29,47 +25,31 @@ Build the core kernel infrastructure (config, logger, DI, health, metrics, obser
|
|||||||
- **Goal:** Implement centralized error handling with an error bus that captures, logs, and optionally reports all application errors.
|
- **Goal:** Implement centralized error handling with an error bus that captures, logs, and optionally reports all application errors.
|
||||||
- **Deliverables:** Error bus interface, channel-based implementation, panic recovery middleware
|
- **Deliverables:** Error bus interface, channel-based implementation, panic recovery middleware
|
||||||
|
|
||||||
### 1.5 HTTP/gRPC Server Foundation
|
### 1.5 HTTP Server Foundation with Middleware Stack
|
||||||
- [Story: 1.5 - HTTP/gRPC Server Foundation](./1.5-http-server.md)
|
- [Story: 1.5 - HTTP Server](./1.5-http-server.md)
|
||||||
- **Goal:** Create HTTP and gRPC server foundation that services can use. Each service will have its own server instance.
|
- **Goal:** Create a production-ready HTTP server with comprehensive middleware for security, observability, and error handling.
|
||||||
- **Deliverables:** HTTP server foundation, gRPC server foundation, common middleware, lifecycle management
|
- **Deliverables:** HTTP server, comprehensive middleware stack, core routes, FX lifecycle integration
|
||||||
|
|
||||||
### 1.6 OpenTelemetry Distributed Tracing
|
### 1.6 OpenTelemetry Distributed Tracing
|
||||||
- [Story: 1.6 - OpenTelemetry](./1.6-opentelemetry.md)
|
- [Story: 1.6 - OpenTelemetry](./1.6-opentelemetry.md)
|
||||||
- **Goal:** Integrate OpenTelemetry for distributed tracing across all services to enable observability in production.
|
- **Goal:** Integrate OpenTelemetry for distributed tracing across the platform to enable observability in production.
|
||||||
- **Deliverables:** OpenTelemetry setup, HTTP instrumentation, gRPC instrumentation, database instrumentation, trace-log correlation
|
- **Deliverables:** OpenTelemetry setup, HTTP instrumentation, database instrumentation, trace-log correlation
|
||||||
|
|
||||||
### 1.7 Service Client Interfaces
|
|
||||||
- [Story: 1.7 - Service Client Interfaces](./1.7-service-client-interfaces.md)
|
|
||||||
- **Goal:** Create service client interfaces for all core services to enable microservices communication.
|
|
||||||
- **Deliverables:** Service client interfaces in `pkg/services/`, service client factory, gRPC/HTTP client implementations
|
|
||||||
|
|
||||||
### 1.8 API Gateway Implementation
|
|
||||||
- [Story: 1.8 - API Gateway](./1.8-api-gateway.md)
|
|
||||||
- **Goal:** Implement API Gateway as core infrastructure component that routes all external traffic to backend services.
|
|
||||||
- **Deliverables:** API Gateway service entry point, gateway implementation with routing, JWT validation, rate limiting, service discovery integration
|
|
||||||
|
|
||||||
## Deliverables Checklist
|
## Deliverables Checklist
|
||||||
- [x] DI container with core kernel services only (no business logic)
|
- [x] DI container with all core services
|
||||||
- [x] Database client foundation (per-service connections)
|
- [x] Database client with Ent schema
|
||||||
- [x] Health and metrics endpoints functional
|
- [x] Health and metrics endpoints functional
|
||||||
- [x] Error bus captures and logs errors
|
- [x] Error bus captures and logs errors
|
||||||
- [x] HTTP/gRPC server foundation for services
|
- [x] HTTP server with middleware stack
|
||||||
- [x] Basic observability with OpenTelemetry
|
- [x] Basic observability with OpenTelemetry
|
||||||
- [x] Service client interfaces defined
|
|
||||||
- [x] API Gateway service (core infrastructure)
|
|
||||||
- [x] Basic service registry implementation (Consul)
|
|
||||||
|
|
||||||
## Acceptance Criteria
|
## Acceptance Criteria
|
||||||
- `GET /healthz` returns 200 for all services
|
- `GET /healthz` returns 200
|
||||||
- `GET /ready` checks service health
|
- `GET /ready` checks DB connectivity
|
||||||
- `GET /metrics` exposes Prometheus metrics
|
- `GET /metrics` exposes Prometheus metrics
|
||||||
- Panic recovery logs errors via error bus
|
- Panic recovery logs errors via error bus
|
||||||
- HTTP/gRPC requests are traced with OpenTelemetry
|
- Database migrations run on startup
|
||||||
- API Gateway routes requests to backend services
|
- HTTP requests are traced with OpenTelemetry
|
||||||
- Service client interfaces are defined
|
|
||||||
- Services can register with Consul
|
|
||||||
- No business logic services in Epic 1
|
|
||||||
|
|
||||||
## Implementation Summary
|
## Implementation Summary
|
||||||
|
|
||||||
|
|||||||
@@ -1,112 +1,106 @@
|
|||||||
# Story 2.1: Auth Service - JWT Authentication
|
# Story 2.1: JWT Authentication System
|
||||||
|
|
||||||
## Metadata
|
## Metadata
|
||||||
- **Story ID**: 2.1
|
- **Story ID**: 2.1
|
||||||
- **Title**: Auth Service - JWT Authentication
|
- **Title**: JWT Authentication System
|
||||||
- **Epic**: 2 - Core Services (Authentication & Authorization)
|
- **Epic**: 2 - Authentication & Authorization
|
||||||
- **Status**: Pending
|
- **Status**: Pending
|
||||||
- **Priority**: High
|
- **Priority**: High
|
||||||
- **Estimated Time**: 8-10 hours
|
- **Estimated Time**: 6-8 hours
|
||||||
- **Dependencies**: 1.1, 1.2, 1.5, 1.7
|
- **Dependencies**: 1.2, 1.5
|
||||||
|
|
||||||
## Goal
|
## Goal
|
||||||
Implement Auth Service as an independent microservice with JWT token generation/validation. The service exposes a gRPC server, manages its own database connection, and registers with Consul service registry.
|
Implement a complete JWT-based authentication system with access tokens, refresh tokens, and secure token management.
|
||||||
|
|
||||||
## Description
|
## Description
|
||||||
This story implements the Auth Service as a separate, independently deployable microservice. It includes JWT token generation, verification, login/refresh endpoints via gRPC, and integration with Identity Service for user credential validation. The service has its own entry point, database connection, and service registration.
|
This story implements the complete JWT authentication system including token generation, verification, authentication middleware, and login/refresh endpoints. The system supports short-lived access tokens and long-lived refresh tokens for secure authentication.
|
||||||
|
|
||||||
## Deliverables
|
## Deliverables
|
||||||
|
|
||||||
### 1. Service Entry Point (`cmd/auth-service/main.go`)
|
### 1. Authentication Interfaces (`pkg/auth/auth.go`)
|
||||||
- Independent service entry point
|
- `Authenticator` interface for token generation and verification
|
||||||
- Bootstrap with core kernel services
|
- `TokenClaims` struct with user ID, roles, tenant ID, expiration
|
||||||
- Register with Consul service registry
|
- Token validation utilities
|
||||||
- Start gRPC server on configured port (default: 8081)
|
|
||||||
- Graceful shutdown with service deregistration
|
|
||||||
|
|
||||||
### 2. gRPC Service Definition (`api/proto/auth.proto`)
|
### 2. JWT Implementation (`internal/auth/jwt_auth.go`)
|
||||||
- `LoginRequest` / `LoginResponse` - User login
|
- Generate short-lived access tokens (15 minutes default)
|
||||||
- `RefreshTokenRequest` / `RefreshTokenResponse` - Token refresh
|
- Generate long-lived refresh tokens (7 days default)
|
||||||
- `ValidateTokenRequest` / `ValidateTokenResponse` - Token validation
|
- Token signature verification using HMAC or RSA
|
||||||
- `AuthService` gRPC service definition
|
|
||||||
|
|
||||||
### 3. gRPC Server Implementation (`services/auth/internal/api/server.go`)
|
|
||||||
- gRPC server implementation
|
|
||||||
- Handler for Login, RefreshToken, ValidateToken
|
|
||||||
- Integration with Auth Service business logic
|
|
||||||
|
|
||||||
### 4. Auth Service Implementation (`services/auth/internal/service/auth_service.go`)
|
|
||||||
- JWT token generation (access tokens: 15 min, refresh tokens: 7 days)
|
|
||||||
- Token signature verification (HMAC or RSA)
|
|
||||||
- Token expiration validation
|
- Token expiration validation
|
||||||
- Claims extraction and validation
|
- Claims extraction and validation
|
||||||
- Uses `IdentityServiceClient` for credential validation
|
|
||||||
|
|
||||||
### 5. Database Connection and Schema (`services/auth/ent/schema/`)
|
### 3. Authentication Middleware (`internal/auth/middleware.go`)
|
||||||
- Auth Service database connection (schema: `auth`)
|
- Extract JWT from `Authorization: Bearer <token>` header
|
||||||
- Refresh token storage schema (if storing refresh tokens in DB)
|
- Verify token validity (signature and expiration)
|
||||||
- Migration support
|
- Inject authenticated user into request context
|
||||||
- Per-service connection pool
|
- Helper function: `auth.FromContext(ctx) *User`
|
||||||
|
- Handle authentication errors appropriately
|
||||||
|
|
||||||
### 6. Service Client Integration
|
### 4. Authentication Endpoints
|
||||||
- Uses `IdentityServiceClient` to validate user credentials
|
- `POST /api/v1/auth/login` - Authenticate user and return tokens
|
||||||
- Uses `AuditServiceClient` to log authentication events
|
- Validate email and password
|
||||||
- Service discovery via Consul
|
- Return access + refresh tokens
|
||||||
|
- Log login attempts
|
||||||
|
- `POST /api/v1/auth/refresh` - Refresh access token using refresh token
|
||||||
|
- Validate refresh token
|
||||||
|
- Issue new access token
|
||||||
|
- Optionally rotate refresh token
|
||||||
|
|
||||||
### 7. Service Registration
|
### 5. gRPC Server (Microservices)
|
||||||
- Register with Consul on startup
|
- Expose gRPC server for authentication service
|
||||||
- Health check endpoint for Consul
|
- gRPC service definition in `api/proto/auth.proto`
|
||||||
- Service metadata (name: `auth-service`, port: 8081)
|
- gRPC server implementation in `internal/auth/grpc/server.go`
|
||||||
- Deregister on shutdown
|
- Service registration in service registry
|
||||||
|
|
||||||
|
### 6. Integration
|
||||||
|
- Integration with DI container
|
||||||
|
- Use `IdentityServiceClient` for user operations (if Identity service is separate)
|
||||||
|
- Integration with HTTP server
|
||||||
|
- Integration with user repository
|
||||||
|
- Integration with audit logging
|
||||||
|
|
||||||
## Implementation Steps
|
## Implementation Steps
|
||||||
|
|
||||||
1. **Create Service Entry Point**
|
1. **Install Dependencies**
|
||||||
- Create `cmd/auth-service/main.go`
|
```bash
|
||||||
- Bootstrap with core kernel (config, logger, DI, health, metrics)
|
go get github.com/golang-jwt/jwt/v5
|
||||||
- Create database connection (auth schema)
|
```
|
||||||
- Register with Consul service registry
|
|
||||||
- Start gRPC server
|
|
||||||
|
|
||||||
2. **Define gRPC Service**
|
2. **Create Authentication Interfaces**
|
||||||
- Create `api/proto/auth.proto`
|
- Create `pkg/auth/auth.go`
|
||||||
- Define Login, RefreshToken, ValidateToken RPCs
|
- Define Authenticator interface
|
||||||
- Generate Go code from proto
|
- Define TokenClaims struct
|
||||||
|
|
||||||
3. **Implement Auth Service**
|
3. **Implement JWT Authentication**
|
||||||
- Create `services/auth/internal/service/auth_service.go`
|
- Create `internal/auth/jwt_auth.go`
|
||||||
- Implement JWT token generation/validation
|
- Implement token generation
|
||||||
- Integrate with IdentityServiceClient for credential validation
|
- Implement token verification
|
||||||
- Integrate with AuditServiceClient for logging
|
- Handle token expiration
|
||||||
|
|
||||||
4. **Implement gRPC Server**
|
4. **Create Authentication Middleware**
|
||||||
- Create `services/auth/internal/api/server.go`
|
- Create `internal/auth/middleware.go`
|
||||||
- Implement gRPC handlers
|
- Implement token extraction
|
||||||
- Wire up service logic
|
- Implement token verification
|
||||||
|
- Inject user into context
|
||||||
|
|
||||||
5. **Database Setup**
|
5. **Create Authentication Endpoints**
|
||||||
- Create `services/auth/ent/schema/` if storing refresh tokens
|
- Create login handler
|
||||||
- Set up migrations
|
- Create refresh handler
|
||||||
- Configure per-service connection pool
|
- Add routes to HTTP server
|
||||||
|
|
||||||
6. **Service Registration**
|
6. **Integrate with DI**
|
||||||
- Register with Consul on startup
|
- Create provider function
|
||||||
- Set up health check endpoint
|
- Register in container
|
||||||
- Handle graceful shutdown
|
|
||||||
|
|
||||||
## Acceptance Criteria
|
## Acceptance Criteria
|
||||||
- [x] Auth Service is independently deployable
|
- [ ] Users can login and receive access and refresh tokens
|
||||||
- [x] Service entry point exists at `cmd/auth-service/main.go`
|
- [ ] Access tokens expire after configured duration
|
||||||
- [x] Service registers with Consul on startup
|
- [ ] Refresh tokens can be used to obtain new access tokens
|
||||||
- [x] gRPC server starts on configured port (8081)
|
- [ ] Invalid tokens are rejected with appropriate errors
|
||||||
- [x] Login RPC validates credentials via IdentityServiceClient
|
- [ ] Authenticated user is available in request context
|
||||||
- [x] Login RPC returns access and refresh tokens
|
- [ ] Login attempts are logged (success and failure)
|
||||||
- [x] RefreshToken RPC issues new access tokens
|
- [ ] Token secrets are configurable
|
||||||
- [x] ValidateToken RPC validates token signatures and expiration
|
- [ ] Token claims include user ID, roles, and tenant ID
|
||||||
- [x] Service has its own database connection (auth schema)
|
|
||||||
- [x] Service uses AuditServiceClient for logging
|
|
||||||
- [x] Service can be discovered by API Gateway via Consul
|
|
||||||
- [x] Health check endpoint works for Consul
|
|
||||||
|
|
||||||
## Related ADRs
|
## Related ADRs
|
||||||
- [ADR-0017: JWT Token Strategy](../../adr/0017-jwt-token-strategy.md)
|
- [ADR-0017: JWT Token Strategy](../../adr/0017-jwt-token-strategy.md)
|
||||||
@@ -122,28 +116,24 @@ This story implements the Auth Service as a separate, independently deployable m
|
|||||||
|
|
||||||
## Testing
|
## Testing
|
||||||
```bash
|
```bash
|
||||||
# Test Auth Service
|
# Test authentication
|
||||||
go test ./services/auth/...
|
go test ./internal/auth/...
|
||||||
|
|
||||||
# Test service startup
|
# Test login endpoint
|
||||||
go run cmd/auth-service/main.go
|
curl -X POST http://localhost:8080/api/v1/auth/login \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d '{"email":"user@example.com","password":"password"}'
|
||||||
|
|
||||||
# Test gRPC service (via grpcurl or client)
|
# Test refresh endpoint
|
||||||
grpcurl -plaintext localhost:8081 list
|
curl -X POST http://localhost:8080/api/v1/auth/refresh \
|
||||||
grpcurl -plaintext -d '{"email":"user@example.com","password":"password"}' \
|
-H "Authorization: Bearer <refresh_token>"
|
||||||
localhost:8081 auth.AuthService/Login
|
|
||||||
|
|
||||||
# Test service discovery
|
|
||||||
# Verify service is registered in Consul
|
|
||||||
consul catalog services
|
|
||||||
consul catalog service auth-service
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## Files to Create/Modify
|
## Files to Create/Modify
|
||||||
- `cmd/auth-service/main.go` - Service entry point
|
- `pkg/auth/auth.go` - Authentication interfaces
|
||||||
- `api/proto/auth.proto` - gRPC service definition
|
- `internal/auth/jwt_auth.go` - JWT implementation
|
||||||
- `services/auth/internal/api/server.go` - gRPC server implementation
|
- `internal/auth/middleware.go` - Authentication middleware
|
||||||
- `services/auth/internal/service/auth_service.go` - Auth service logic
|
- `internal/auth/handler.go` - Authentication handlers
|
||||||
- `services/auth/ent/schema/` - Database schema (if storing refresh tokens)
|
- `internal/di/providers.go` - Add auth provider
|
||||||
- `config/default.yaml` - Add auth service configuration
|
- `config/default.yaml` - Add JWT configuration
|
||||||
|
|
||||||
|
|||||||
@@ -1,122 +1,82 @@
|
|||||||
# Story 2.2: Identity Service - User Management
|
# Story 2.2: Identity Management System
|
||||||
|
|
||||||
## Metadata
|
## Metadata
|
||||||
- **Story ID**: 2.2
|
- **Story ID**: 2.2
|
||||||
- **Title**: Identity Service - User Management
|
- **Title**: Identity Management System
|
||||||
- **Epic**: 2 - Core Services (Authentication & Authorization)
|
- **Epic**: 2 - Authentication & Authorization
|
||||||
- **Status**: Pending
|
- **Status**: Pending
|
||||||
- **Priority**: High
|
- **Priority**: High
|
||||||
- **Estimated Time**: 10-12 hours
|
- **Estimated Time**: 8-10 hours
|
||||||
- **Dependencies**: 1.1, 1.2, 1.5, 1.7
|
- **Dependencies**: 1.2, 2.1
|
||||||
|
|
||||||
## Goal
|
## Goal
|
||||||
Implement Identity Service as an independent microservice for user CRUD operations, password management, and email verification. The service exposes a gRPC server, manages its own database connection with User entity, and registers with Consul service registry.
|
Build a complete user identity management system with registration, email verification, password management, and user CRUD operations.
|
||||||
|
|
||||||
## Description
|
## Description
|
||||||
This story implements the Identity Service as a separate, independently deployable microservice. It includes user registration, email verification, password reset/change, and user profile management via gRPC. The service has its own entry point, database connection with User entity schema, and service registration.
|
This story implements the complete user identity management system including user registration, email verification, password reset, password change, and user profile management. All operations are secured and audited.
|
||||||
|
|
||||||
## Deliverables
|
## Deliverables
|
||||||
|
|
||||||
### 1. Service Entry Point (`cmd/identity-service/main.go`)
|
### 1. Identity Interfaces (`pkg/identity/identity.go`)
|
||||||
- Independent service entry point
|
- `UserRepository` interface for user data access
|
||||||
- Bootstrap with core kernel services
|
- `UserService` interface for user business logic
|
||||||
- Register with Consul service registry
|
- User domain models
|
||||||
- Start gRPC server on configured port (default: 8082)
|
|
||||||
- Graceful shutdown with service deregistration
|
|
||||||
|
|
||||||
### 2. gRPC Service Definition (`api/proto/identity.proto`)
|
### 2. User Repository (`internal/identity/user_repo.go`)
|
||||||
- `CreateUserRequest` / `CreateUserResponse` - User registration
|
- CRUD operations using Ent
|
||||||
- `GetUserRequest` / `GetUserResponse` - Get user by ID
|
- Password hashing (bcrypt or argon2)
|
||||||
- `GetUserByEmailRequest` / `GetUserByEmailResponse` - Get user by email
|
- Email uniqueness validation
|
||||||
- `UpdateUserRequest` / `UpdateUserResponse` - Update user profile
|
- User lookup by ID and email
|
||||||
- `DeleteUserRequest` / `DeleteUserResponse` - Delete user
|
- User search and pagination
|
||||||
- `VerifyEmailRequest` / `VerifyEmailResponse` - Email verification
|
|
||||||
- `RequestPasswordResetRequest` / `RequestPasswordResetResponse` - Password reset request
|
|
||||||
- `ResetPasswordRequest` / `ResetPasswordResponse` - Password reset
|
|
||||||
- `ChangePasswordRequest` / `ChangePasswordResponse` - Password change
|
|
||||||
- `IdentityService` gRPC service definition
|
|
||||||
|
|
||||||
### 3. gRPC Server Implementation (`services/identity/internal/api/server.go`)
|
### 3. User Service (`internal/identity/user_service.go`)
|
||||||
- gRPC server implementation
|
|
||||||
- Handlers for all user operations
|
|
||||||
- Integration with Identity Service business logic
|
|
||||||
|
|
||||||
### 4. Identity Service Implementation (`services/identity/internal/service/user_service.go`)
|
|
||||||
- User registration with email verification token generation
|
- User registration with email verification token generation
|
||||||
- Email verification flow
|
- Email verification flow
|
||||||
- Password reset flow (token-based, time-limited)
|
- Password reset flow (token-based, time-limited)
|
||||||
- Password change with old password verification
|
- Password change with old password verification
|
||||||
- User profile updates
|
- User profile updates
|
||||||
- User deletion (soft delete option)
|
- User deletion (soft delete option)
|
||||||
- Password hashing (argon2id)
|
|
||||||
- Email uniqueness validation
|
|
||||||
|
|
||||||
### 5. User Repository (`services/identity/internal/repository/user_repo.go`)
|
### 4. User Management API Endpoints
|
||||||
- CRUD operations using Ent
|
- `POST /api/v1/users` - Register new user
|
||||||
- User lookup by ID and email
|
- `GET /api/v1/users/:id` - Get user profile (authorized)
|
||||||
- User search and pagination
|
- `PUT /api/v1/users/:id` - Update user profile (authorized)
|
||||||
- Ent schema integration
|
- `DELETE /api/v1/users/:id` - Delete user (admin only)
|
||||||
|
- `POST /api/v1/users/verify-email` - Verify email with token
|
||||||
|
- `POST /api/v1/users/reset-password` - Request password reset
|
||||||
|
- `POST /api/v1/users/change-password` - Change password
|
||||||
|
|
||||||
### 6. Database Connection and Schema (`services/identity/ent/schema/user.go`)
|
### 5. gRPC Server (Microservices)
|
||||||
- Identity Service database connection (schema: `identity`)
|
- Expose gRPC server for identity service
|
||||||
- User entity schema:
|
- gRPC service definition in `api/proto/identity.proto`
|
||||||
- ID, email, password_hash, verified, created_at, updated_at
|
- gRPC server implementation in `internal/identity/grpc/server.go`
|
||||||
- Email verification token, password reset token
|
- Service registration in service registry
|
||||||
- Migration support
|
|
||||||
- Per-service connection pool
|
|
||||||
|
|
||||||
### 7. Service Client Integration
|
### 6. Integration
|
||||||
- Uses `AuditServiceClient` to log user operations
|
- Integration with email notification system (Epic 5 placeholder)
|
||||||
- Service discovery via Consul
|
- Integration with audit logging
|
||||||
|
- Integration with authentication system
|
||||||
### 8. Service Registration
|
- Identity service is an independent service that can be deployed separately
|
||||||
- Register with Consul on startup
|
|
||||||
- Health check endpoint for Consul
|
|
||||||
- Service metadata (name: `identity-service`, port: 8082)
|
|
||||||
- Deregister on shutdown
|
|
||||||
|
|
||||||
## Acceptance Criteria
|
## Acceptance Criteria
|
||||||
- [x] Identity Service is independently deployable
|
- [ ] Users can register with email and password
|
||||||
- [x] Service entry point exists at `cmd/identity-service/main.go`
|
- [ ] Passwords are securely hashed
|
||||||
- [x] Service registers with Consul on startup
|
- [ ] Email verification tokens are generated and validated
|
||||||
- [x] gRPC server starts on configured port (8082)
|
- [ ] Password reset flow works end-to-end
|
||||||
- [x] CreateUser RPC registers new users with password hashing
|
- [ ] Users can update their profiles
|
||||||
- [x] GetUser/GetUserByEmail RPCs retrieve user data
|
- [ ] User operations require proper authentication
|
||||||
- [x] UpdateUser RPC updates user profiles
|
- [ ] All user actions are audited
|
||||||
- [x] VerifyEmail RPC verifies email addresses
|
- [ ] Email uniqueness is enforced
|
||||||
- [x] Password reset flow works via RPCs
|
|
||||||
- [x] Service has its own database connection (identity schema)
|
|
||||||
- [x] User entity schema is defined and migrated
|
|
||||||
- [x] Service uses AuditServiceClient for logging
|
|
||||||
- [x] Service can be discovered by other services via Consul
|
|
||||||
- [x] Health check endpoint works for Consul
|
|
||||||
|
|
||||||
## Related ADRs
|
## Related ADRs
|
||||||
- [ADR-0018: Password Hashing](../../adr/0018-password-hashing.md)
|
- [ADR-0018: Password Hashing](../../adr/0018-password-hashing.md)
|
||||||
- [ADR-0029: Microservices Architecture](../../adr/0029-microservices-architecture.md)
|
- [ADR-0029: Microservices Architecture](../../adr/0029-microservices-architecture.md)
|
||||||
- [ADR-0030: Service Communication Strategy](../../adr/0030-service-communication-strategy.md)
|
- [ADR-0030: Service Communication Strategy](../../adr/0030-service-communication-strategy.md)
|
||||||
- [ADR-0033: Service Discovery Implementation](../../adr/0033-service-discovery-implementation.md)
|
|
||||||
|
|
||||||
## Testing
|
|
||||||
```bash
|
|
||||||
# Test Identity Service
|
|
||||||
go test ./services/identity/...
|
|
||||||
|
|
||||||
# Test service startup
|
|
||||||
go run cmd/identity-service/main.go
|
|
||||||
|
|
||||||
# Test gRPC service
|
|
||||||
grpcurl -plaintext localhost:8082 list
|
|
||||||
grpcurl -plaintext -d '{"email":"user@example.com","password":"password"}' \
|
|
||||||
localhost:8082 identity.IdentityService/CreateUser
|
|
||||||
```
|
|
||||||
|
|
||||||
## Files to Create/Modify
|
## Files to Create/Modify
|
||||||
- `cmd/identity-service/main.go` - Service entry point
|
- `pkg/identity/identity.go` - Identity interfaces
|
||||||
- `api/proto/identity.proto` - gRPC service definition
|
- `internal/identity/user_repo.go` - User repository
|
||||||
- `services/identity/internal/api/server.go` - gRPC server implementation
|
- `internal/identity/user_service.go` - User service
|
||||||
- `services/identity/internal/service/user_service.go` - User service logic
|
- `internal/identity/handler.go` - User handlers
|
||||||
- `services/identity/internal/repository/user_repo.go` - User repository
|
- `internal/di/providers.go` - Add identity providers
|
||||||
- `services/identity/ent/schema/user.go` - User entity schema
|
|
||||||
- `config/default.yaml` - Add identity service configuration
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,118 +1,70 @@
|
|||||||
# Story 2.3: Authz Service - Authorization & RBAC
|
# Story 2.3: Role-Based Access Control (RBAC) System
|
||||||
|
|
||||||
## Metadata
|
## Metadata
|
||||||
- **Story ID**: 2.3
|
- **Story ID**: 2.3
|
||||||
- **Title**: Authz Service - Authorization & RBAC
|
- **Title**: Role-Based Access Control (RBAC) System
|
||||||
- **Epic**: 2 - Core Services (Authentication & Authorization)
|
- **Epic**: 2 - Authentication & Authorization
|
||||||
- **Status**: Pending
|
- **Status**: Pending
|
||||||
- **Priority**: High
|
- **Priority**: High
|
||||||
- **Estimated Time**: 10-12 hours
|
- **Estimated Time**: 6-8 hours
|
||||||
- **Dependencies**: 1.1, 1.2, 1.5, 1.7, 2.2
|
- **Dependencies**: 1.2, 2.1
|
||||||
|
|
||||||
## Goal
|
## Goal
|
||||||
Implement Authz Service as an independent microservice for permission resolution and authorization checks. The service exposes a gRPC server, manages its own database connection with Role and Permission entities, and registers with Consul service registry.
|
Implement a complete RBAC system with permissions, role management, and authorization middleware.
|
||||||
|
|
||||||
## Description
|
## Description
|
||||||
This story implements the Authz Service as a separate, independently deployable microservice. It includes permission resolution, RBAC/ABAC authorization checks, role-permission management, and user-role assignment via gRPC. The service has its own entry point, database connection with Role and Permission entity schemas, and service registration.
|
This story implements the complete RBAC system including permission definitions, permission resolution, authorization checking, and middleware for protecting routes.
|
||||||
|
|
||||||
## Deliverables
|
## Deliverables
|
||||||
|
|
||||||
### 1. Service Entry Point (`cmd/authz-service/main.go`)
|
### 1. Permission System (`pkg/perm/perm.go`)
|
||||||
- Independent service entry point
|
|
||||||
- Bootstrap with core kernel services
|
|
||||||
- Register with Consul service registry
|
|
||||||
- Start gRPC server on configured port (default: 8083)
|
|
||||||
- Graceful shutdown with service deregistration
|
|
||||||
|
|
||||||
### 2. gRPC Service Definition (`api/proto/authz.proto`)
|
|
||||||
- `AuthorizeRequest` / `AuthorizeResponse` - Check if user has permission
|
|
||||||
- `HasPermissionRequest` / `HasPermissionResponse` - Boolean permission check
|
|
||||||
- `GetUserPermissionsRequest` / `GetUserPermissionsResponse` - Get all user permissions
|
|
||||||
- `GetUserRolesRequest` / `GetUserRolesResponse` - Get user roles
|
|
||||||
- `AuthzService` gRPC service definition
|
|
||||||
|
|
||||||
### 3. gRPC Server Implementation (`services/authz/internal/api/server.go`)
|
|
||||||
- gRPC server implementation
|
|
||||||
- Handlers for authorization operations
|
|
||||||
- Integration with Authz Service business logic
|
|
||||||
|
|
||||||
### 4. Authz Service Implementation (`services/authz/internal/service/authz_service.go`)
|
|
||||||
- Permission resolution from user roles
|
|
||||||
- RBAC authorization checks
|
|
||||||
- Permission caching (Redis)
|
|
||||||
- Uses `IdentityServiceClient` to get user roles
|
|
||||||
- Permission inheritance via roles
|
|
||||||
|
|
||||||
### 5. Permission System (`pkg/perm/perm.go`)
|
|
||||||
- `Permission` type (string format: "module.resource.action")
|
- `Permission` type (string format: "module.resource.action")
|
||||||
- Core permission constants
|
- Core permission constants (system, user, role permissions)
|
||||||
- Permission validation utilities
|
- Permission validation utilities
|
||||||
|
|
||||||
### 6. Database Connection and Schema (`services/authz/ent/schema/`)
|
### 2. Permission Resolver (`pkg/perm/resolver.go` & `internal/perm/in_memory_resolver.go`)
|
||||||
- Authz Service database connection (schema: `authz`)
|
- `PermissionResolver` interface
|
||||||
- Role entity schema: ID, name, description, created_at
|
- Implementation that loads user roles and permissions from database
|
||||||
- Permission entity schema: ID, name (format: "module.resource.action")
|
- Permission checking with caching
|
||||||
- RolePermission entity (many-to-many relationship)
|
- Permission inheritance via roles
|
||||||
- UserRole entity (many-to-many, references Identity Service users)
|
|
||||||
- Migration support
|
|
||||||
- Per-service connection pool
|
|
||||||
|
|
||||||
### 7. Service Client Integration
|
### 3. Authorization System (`pkg/auth/authz.go` & `internal/auth/rbac_authorizer.go`)
|
||||||
- Uses `IdentityServiceClient` to get user roles
|
- `Authorizer` interface
|
||||||
- Uses `AuditServiceClient` to log authorization checks
|
- RBAC authorizer implementation
|
||||||
- Service discovery via Consul
|
- Extract user from context
|
||||||
|
- Check permissions
|
||||||
|
- Return authorization errors
|
||||||
|
|
||||||
### 8. Service Registration
|
### 4. Authorization Middleware
|
||||||
- Register with Consul on startup
|
- `RequirePermission(perm Permission) gin.HandlerFunc` decorator
|
||||||
- Health check endpoint for Consul
|
- Integration with route registration
|
||||||
- Service metadata (name: `authz-service`, port: 8083)
|
- Proper error responses for unauthorized access
|
||||||
- Deregister on shutdown
|
|
||||||
|
### 5. gRPC Server (Microservices)
|
||||||
|
- Expose gRPC server for authorization service
|
||||||
|
- gRPC service definition in `api/proto/authz.proto`
|
||||||
|
- gRPC server implementation in `internal/auth/grpc/authz_server.go`
|
||||||
|
- Service registration in service registry
|
||||||
|
- Uses `IdentityServiceClient` for user operations
|
||||||
|
|
||||||
## Acceptance Criteria
|
## Acceptance Criteria
|
||||||
- [x] Authz Service is independently deployable
|
- [ ] Permissions are defined and can be checked
|
||||||
- [x] Service entry point exists at `cmd/authz-service/main.go`
|
- [ ] Users inherit permissions through roles
|
||||||
- [x] Service registers with Consul on startup
|
- [ ] Authorization middleware protects routes
|
||||||
- [x] gRPC server starts on configured port (8083)
|
- [ ] Unauthorized requests return 403 errors
|
||||||
- [x] Authorize RPC checks if user has permission
|
- [ ] Permission checks are cached for performance
|
||||||
- [x] HasPermission RPC returns boolean permission check
|
- [ ] Permission system is extensible by modules
|
||||||
- [x] GetUserPermissions RPC returns all user permissions
|
|
||||||
- [x] Users inherit permissions through roles
|
|
||||||
- [x] Permission checks are cached (Redis)
|
|
||||||
- [x] Service has its own database connection (authz schema)
|
|
||||||
- [x] Role and Permission entity schemas are defined and migrated
|
|
||||||
- [x] Service uses IdentityServiceClient to get user roles
|
|
||||||
- [x] Service uses AuditServiceClient for logging
|
|
||||||
- [x] Service can be discovered by other services via Consul
|
|
||||||
- [x] Health check endpoint works for Consul
|
|
||||||
|
|
||||||
## Related ADRs
|
## Related ADRs
|
||||||
- [ADR-0019: Permission DSL Format](../../adr/0019-permission-dsl-format.md)
|
- [ADR-0019: Permission DSL Format](../../adr/0019-permission-dsl-format.md)
|
||||||
- [ADR-0029: Microservices Architecture](../../adr/0029-microservices-architecture.md)
|
- [ADR-0029: Microservices Architecture](../../adr/0029-microservices-architecture.md)
|
||||||
- [ADR-0030: Service Communication Strategy](../../adr/0030-service-communication-strategy.md)
|
- [ADR-0030: Service Communication Strategy](../../adr/0030-service-communication-strategy.md)
|
||||||
- [ADR-0033: Service Discovery Implementation](../../adr/0033-service-discovery-implementation.md)
|
|
||||||
|
|
||||||
## Testing
|
|
||||||
```bash
|
|
||||||
# Test Authz Service
|
|
||||||
go test ./services/authz/...
|
|
||||||
|
|
||||||
# Test service startup
|
|
||||||
go run cmd/authz-service/main.go
|
|
||||||
|
|
||||||
# Test gRPC service
|
|
||||||
grpcurl -plaintext localhost:8083 list
|
|
||||||
grpcurl -plaintext -d '{"user_id":"123","permission":"blog.post.create"}' \
|
|
||||||
localhost:8083 authz.AuthzService/Authorize
|
|
||||||
```
|
|
||||||
|
|
||||||
## Files to Create/Modify
|
## Files to Create/Modify
|
||||||
- `cmd/authz-service/main.go` - Service entry point
|
|
||||||
- `api/proto/authz.proto` - gRPC service definition
|
|
||||||
- `services/authz/internal/api/server.go` - gRPC server implementation
|
|
||||||
- `services/authz/internal/service/authz_service.go` - Authz service logic
|
|
||||||
- `services/authz/ent/schema/role.go` - Role entity schema
|
|
||||||
- `services/authz/ent/schema/permission.go` - Permission entity schema
|
|
||||||
- `services/authz/ent/schema/role_permission.go` - Relationship schema
|
|
||||||
- `pkg/perm/perm.go` - Permission types
|
- `pkg/perm/perm.go` - Permission types
|
||||||
- `config/default.yaml` - Add authz service configuration
|
- `pkg/perm/resolver.go` - Permission resolver interface
|
||||||
|
- `internal/perm/in_memory_resolver.go` - Permission resolver implementation
|
||||||
|
- `pkg/auth/authz.go` - Authorization interface
|
||||||
|
- `internal/auth/rbac_authorizer.go` - RBAC authorizer
|
||||||
|
- `internal/auth/middleware.go` - Add authorization middleware
|
||||||
|
|
||||||
|
|||||||
@@ -1,87 +1,64 @@
|
|||||||
# Story 2.4: Role Management (Part of Authz Service)
|
# Story 2.4: Role Management API
|
||||||
|
|
||||||
## Metadata
|
## Metadata
|
||||||
- **Story ID**: 2.4
|
- **Story ID**: 2.4
|
||||||
- **Title**: Role Management (Part of Authz Service)
|
- **Title**: Role Management API
|
||||||
- **Epic**: 2 - Core Services (Authentication & Authorization)
|
- **Epic**: 2 - Authentication & Authorization
|
||||||
- **Status**: Pending
|
- **Status**: Pending
|
||||||
- **Priority**: High
|
- **Priority**: High
|
||||||
- **Estimated Time**: 6-8 hours
|
- **Estimated Time**: 5-6 hours
|
||||||
- **Dependencies**: 2.3
|
- **Dependencies**: 1.2, 2.3
|
||||||
|
|
||||||
## Goal
|
## Goal
|
||||||
Extend Authz Service with role management gRPC endpoints for creating, updating, and deleting roles, assigning permissions to roles, and assigning roles to users.
|
Provide complete API for managing roles, assigning permissions to roles, and assigning roles to users.
|
||||||
|
|
||||||
## Description
|
## Description
|
||||||
This story extends the Authz Service (implemented in Story 2.3) with role management capabilities. It adds gRPC endpoints for role CRUD operations, permission assignment to roles, and role assignment to users. The service uses IdentityServiceClient to manage user-role relationships.
|
This story implements the complete role management API allowing administrators to create, update, and delete roles, assign permissions to roles, and assign roles to users.
|
||||||
|
|
||||||
## Deliverables
|
## Deliverables
|
||||||
|
|
||||||
### 1. gRPC Service Extensions (`api/proto/authz.proto`)
|
### 1. Role Repository (`internal/identity/role_repo.go`)
|
||||||
Extend Authz Service proto with role management RPCs:
|
- CRUD operations for roles
|
||||||
|
- Assign permissions to roles (many-to-many)
|
||||||
- `CreateRoleRequest` / `CreateRoleResponse` - Create new role
|
- Assign roles to users (many-to-many)
|
||||||
- `GetRoleRequest` / `GetRoleResponse` - Get role details
|
|
||||||
- `ListRolesRequest` / `ListRolesResponse` - List all roles (with pagination)
|
|
||||||
- `UpdateRoleRequest` / `UpdateRoleResponse` - Update role
|
|
||||||
- `DeleteRoleRequest` / `DeleteRoleResponse` - Delete role
|
|
||||||
- `AssignPermissionToRoleRequest` / `AssignPermissionToRoleResponse` - Assign permission to role
|
|
||||||
- `RemovePermissionFromRoleRequest` / `RemovePermissionFromRoleResponse` - Remove permission from role
|
|
||||||
- `AssignRoleToUserRequest` / `AssignRoleToUserResponse` - Assign role to user (via IdentityServiceClient)
|
|
||||||
- `RemoveRoleFromUserRequest` / `RemoveRoleFromUserResponse` - Remove role from user (via IdentityServiceClient)
|
|
||||||
|
|
||||||
### 2. Role Repository (`services/authz/internal/repository/role_repo.go`)
|
|
||||||
- CRUD operations for roles using Ent
|
|
||||||
- Assign permissions to roles (many-to-many via RolePermission entity)
|
|
||||||
- List roles with permissions
|
- List roles with permissions
|
||||||
- Integration with Authz Service database (authz schema)
|
- List users with roles
|
||||||
|
|
||||||
### 3. Role Service (`services/authz/internal/service/role_service.go`)
|
### 2. Role Management API Endpoints
|
||||||
- Role management business logic
|
- `POST /api/v1/roles` - Create new role
|
||||||
- Permission assignment to roles
|
- `GET /api/v1/roles` - List all roles (with pagination)
|
||||||
- Role assignment to users (via IdentityServiceClient)
|
- `GET /api/v1/roles/:id` - Get role details with permissions
|
||||||
|
- `PUT /api/v1/roles/:id` - Update role
|
||||||
|
- `DELETE /api/v1/roles/:id` - Delete role
|
||||||
|
- `POST /api/v1/roles/:id/permissions` - Assign permissions to role
|
||||||
|
- `DELETE /api/v1/roles/:id/permissions/:permId` - Remove permission from role
|
||||||
|
- `POST /api/v1/users/:id/roles` - Assign roles to user
|
||||||
|
- `DELETE /api/v1/users/:id/roles/:roleId` - Remove role from user
|
||||||
|
|
||||||
|
### 3. Authorization and Validation
|
||||||
|
- All endpoints protected (admin only)
|
||||||
- Input validation
|
- Input validation
|
||||||
- Error handling
|
- Error handling
|
||||||
|
|
||||||
### 4. gRPC Server Extensions (`services/authz/internal/api/server.go`)
|
### 4. gRPC Server (Microservices)
|
||||||
- Add role management handlers to existing Authz Service gRPC server
|
- Expose role management via existing Authz service gRPC server
|
||||||
- Integration with Role Service
|
- Role management methods in `api/proto/authz.proto`
|
||||||
- Authorization checks (admin only for role management)
|
- Service registration in service registry
|
||||||
|
|
||||||
### 5. Service Client Integration
|
|
||||||
- Uses `IdentityServiceClient` to manage user-role relationships
|
|
||||||
- Uses `AuditServiceClient` to log role management operations
|
|
||||||
|
|
||||||
## Acceptance Criteria
|
## Acceptance Criteria
|
||||||
- [x] CreateRole RPC creates new roles
|
- [ ] Admin users can create and manage roles
|
||||||
- [x] GetRole/ListRoles RPCs retrieve role data
|
- [ ] Permissions can be assigned to roles
|
||||||
- [x] UpdateRole/DeleteRole RPCs modify roles
|
- [ ] Roles can be assigned to users
|
||||||
- [x] AssignPermissionToRole RPC assigns permissions to roles
|
- [ ] Role changes affect user permissions immediately
|
||||||
- [x] AssignRoleToUser RPC assigns roles to users (via IdentityServiceClient)
|
- [ ] All role operations are audited
|
||||||
- [x] Role changes affect user permissions immediately (cache invalidation)
|
- [ ] API endpoints are protected with proper permissions
|
||||||
- [x] All role operations are audited via AuditServiceClient
|
|
||||||
- [x] Role management RPCs are protected with proper permissions
|
|
||||||
- [x] Service uses IdentityServiceClient for user-role relationships
|
|
||||||
|
|
||||||
## Related ADRs
|
## Related ADRs
|
||||||
- [ADR-0029: Microservices Architecture](../../adr/0029-microservices-architecture.md)
|
- [ADR-0029: Microservices Architecture](../../adr/0029-microservices-architecture.md)
|
||||||
- [ADR-0030: Service Communication Strategy](../../adr/0030-service-communication-strategy.md)
|
- [ADR-0030: Service Communication Strategy](../../adr/0030-service-communication-strategy.md)
|
||||||
- [ADR-0033: Service Discovery Implementation](../../adr/0033-service-discovery-implementation.md)
|
|
||||||
|
|
||||||
## Testing
|
|
||||||
```bash
|
|
||||||
# Test role management
|
|
||||||
go test ./services/authz/...
|
|
||||||
|
|
||||||
# Test gRPC service
|
|
||||||
grpcurl -plaintext localhost:8083 list
|
|
||||||
grpcurl -plaintext -d '{"name":"admin","description":"Administrator role"}' \
|
|
||||||
localhost:8083 authz.AuthzService/CreateRole
|
|
||||||
```
|
|
||||||
|
|
||||||
## Files to Create/Modify
|
## Files to Create/Modify
|
||||||
- `api/proto/authz.proto` - Add role management RPCs
|
- `internal/identity/role_repo.go` - Role repository
|
||||||
- `services/authz/internal/repository/role_repo.go` - Role repository
|
- `internal/identity/role_handler.go` - Role handlers
|
||||||
- `services/authz/internal/service/role_service.go` - Role service logic
|
- `internal/server/routes.go` - Add role routes
|
||||||
- `services/authz/internal/api/server.go` - Add role management handlers
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,105 +1,74 @@
|
|||||||
# Story 2.5: Audit Service - Audit Logging
|
# Story 2.5: Audit Logging System
|
||||||
|
|
||||||
## Metadata
|
## Metadata
|
||||||
- **Story ID**: 2.5
|
- **Story ID**: 2.5
|
||||||
- **Title**: Audit Service - Audit Logging
|
- **Title**: Audit Logging System
|
||||||
- **Epic**: 2 - Core Services (Authentication & Authorization)
|
- **Epic**: 2 - Authentication & Authorization
|
||||||
- **Status**: Pending
|
- **Status**: Pending
|
||||||
- **Priority**: High
|
- **Priority**: High
|
||||||
- **Estimated Time**: 6-8 hours
|
- **Estimated Time**: 5-6 hours
|
||||||
- **Dependencies**: 1.1, 1.2, 1.5, 1.7
|
- **Dependencies**: 1.2, 2.1
|
||||||
|
|
||||||
## Goal
|
## Goal
|
||||||
Implement Audit Service as an independent microservice for audit logging. The service exposes a gRPC server, manages its own database connection with AuditLog entity, and registers with Consul service registry.
|
Implement comprehensive audit logging that records all security-sensitive actions for compliance and security monitoring.
|
||||||
|
|
||||||
## Description
|
## Description
|
||||||
This story implements the Audit Service as a separate, independently deployable microservice. It includes audit log recording and querying via gRPC. The service has its own entry point, database connection with AuditLog entity schema, and service registration. Other services use AuditServiceClient to record audit events.
|
This story implements a complete audit logging system that records all authenticated actions with full context including actor, action, target, and metadata.
|
||||||
|
|
||||||
## Deliverables
|
## Deliverables
|
||||||
|
|
||||||
### 1. Service Entry Point (`cmd/audit-service/main.go`)
|
### 1. Audit Interface (`pkg/audit/audit.go`)
|
||||||
- Independent service entry point
|
- `Auditor` interface with `Record(ctx, action)` method
|
||||||
- Bootstrap with core kernel services
|
|
||||||
- Register with Consul service registry
|
|
||||||
- Start gRPC server on configured port (default: 8084)
|
|
||||||
- Graceful shutdown with service deregistration
|
|
||||||
|
|
||||||
### 2. gRPC Service Definition (`api/proto/audit.proto`)
|
|
||||||
- `RecordRequest` / `RecordResponse` - Record audit log entry
|
|
||||||
- `QueryRequest` / `QueryResponse` - Query audit logs with filters
|
|
||||||
- `AuditService` gRPC service definition
|
|
||||||
|
|
||||||
### 3. gRPC Server Implementation (`services/audit/internal/api/server.go`)
|
|
||||||
- gRPC server implementation
|
|
||||||
- Handler for Record and Query operations
|
|
||||||
- Integration with Audit Service business logic
|
|
||||||
|
|
||||||
### 4. Audit Service Implementation (`services/audit/internal/service/audit_service.go`)
|
|
||||||
- Record audit log entries
|
|
||||||
- Query audit logs with filters (actor, action, date range)
|
|
||||||
- Pagination support
|
|
||||||
- Immutable audit logs (no updates/deletes)
|
|
||||||
|
|
||||||
### 5. Audit Interface (`pkg/services/audit.go`)
|
|
||||||
- `AuditServiceClient` interface (defined in Epic 1, Story 1.7)
|
|
||||||
- `Record(ctx, action)` method
|
|
||||||
- `Query(ctx, filters)` method
|
|
||||||
- `AuditAction` struct with actor, action, target, metadata
|
- `AuditAction` struct with actor, action, target, metadata
|
||||||
|
|
||||||
### 6. Database Connection and Schema (`services/audit/ent/schema/audit_log.go`)
|
### 2. Audit Implementation (`internal/audit/ent_auditor.go`)
|
||||||
- Audit Service database connection (schema: `audit`)
|
- Write audit logs to `audit_log` table
|
||||||
- AuditLog entity schema:
|
- Capture actor from request context
|
||||||
- ID, actor_id, action, target_id, metadata (JSONB), timestamp
|
- Include request metadata (ID, IP, user agent, timestamp)
|
||||||
- Immutable (no update/delete operations)
|
- Store action details and target information
|
||||||
- Migration support
|
- Support JSON metadata for flexible logging
|
||||||
- Per-service connection pool
|
|
||||||
|
|
||||||
### 7. Service Registration
|
### 3. Audit Middleware
|
||||||
- Register with Consul on startup
|
- Intercept all authenticated requests
|
||||||
- Health check endpoint for Consul
|
- Record action (HTTP method + path)
|
||||||
- Service metadata (name: `audit-service`, port: 8084)
|
- Extract user and request context
|
||||||
- Deregister on shutdown
|
- Store audit log entry
|
||||||
|
|
||||||
|
### 4. gRPC Server (Microservices)
|
||||||
|
- Expose gRPC server for audit service
|
||||||
|
- gRPC service definition in `api/proto/audit.proto`
|
||||||
|
- gRPC server implementation in `internal/audit/grpc/server.go`
|
||||||
|
- Service registration in service registry
|
||||||
|
|
||||||
|
### 5. Integration
|
||||||
|
- Integration with authentication endpoints
|
||||||
|
- Log login attempts (success and failure)
|
||||||
|
- Log password changes
|
||||||
|
- Log role assignments and removals
|
||||||
|
- Log permission changes
|
||||||
|
- Log user registration
|
||||||
|
|
||||||
|
### 5. Audit Log Query API
|
||||||
|
- `GET /api/v1/audit-logs` - Query audit logs with filters (admin only)
|
||||||
|
- Support filtering by actor, action, date range
|
||||||
|
- Pagination support
|
||||||
|
|
||||||
## Acceptance Criteria
|
## Acceptance Criteria
|
||||||
- [x] Audit Service is independently deployable
|
- [ ] All authenticated actions are logged
|
||||||
- [x] Service entry point exists at `cmd/audit-service/main.go`
|
- [ ] Audit logs include complete context (actor, action, target, metadata)
|
||||||
- [x] Service registers with Consul on startup
|
- [ ] Audit logs are immutable (no updates/deletes)
|
||||||
- [x] gRPC server starts on configured port (8084)
|
- [ ] Audit logs can be queried and filtered
|
||||||
- [x] Record RPC stores audit log entries
|
- [ ] Audit logging has minimal performance impact
|
||||||
- [x] Query RPC retrieves audit logs with filters
|
- [ ] Audit logs are stored securely
|
||||||
- [x] Audit logs include complete context (actor, action, target, metadata)
|
|
||||||
- [x] Audit logs are immutable (no updates/deletes)
|
|
||||||
- [x] Service has its own database connection (audit schema)
|
|
||||||
- [x] AuditLog entity schema is defined and migrated
|
|
||||||
- [x] Other services can use AuditServiceClient to record events
|
|
||||||
- [x] Service can be discovered by other services via Consul
|
|
||||||
- [x] Health check endpoint works for Consul
|
|
||||||
|
|
||||||
## Related ADRs
|
## Related ADRs
|
||||||
- [ADR-0020: Audit Logging Storage](../../adr/0020-audit-logging-storage.md)
|
- [ADR-0020: Audit Logging Storage](../../adr/0020-audit-logging-storage.md)
|
||||||
- [ADR-0029: Microservices Architecture](../../adr/0029-microservices-architecture.md)
|
- [ADR-0029: Microservices Architecture](../../adr/0029-microservices-architecture.md)
|
||||||
- [ADR-0030: Service Communication Strategy](../../adr/0030-service-communication-strategy.md)
|
- [ADR-0030: Service Communication Strategy](../../adr/0030-service-communication-strategy.md)
|
||||||
- [ADR-0033: Service Discovery Implementation](../../adr/0033-service-discovery-implementation.md)
|
|
||||||
|
|
||||||
## Testing
|
|
||||||
```bash
|
|
||||||
# Test Audit Service
|
|
||||||
go test ./services/audit/...
|
|
||||||
|
|
||||||
# Test service startup
|
|
||||||
go run cmd/audit-service/main.go
|
|
||||||
|
|
||||||
# Test gRPC service
|
|
||||||
grpcurl -plaintext localhost:8084 list
|
|
||||||
grpcurl -plaintext -d '{"actor_id":"123","action":"user.login","target_id":"user-123"}' \
|
|
||||||
localhost:8084 audit.AuditService/Record
|
|
||||||
```
|
|
||||||
|
|
||||||
## Files to Create/Modify
|
## Files to Create/Modify
|
||||||
- `cmd/audit-service/main.go` - Service entry point
|
- `pkg/audit/audit.go` - Audit interface
|
||||||
- `api/proto/audit.proto` - gRPC service definition
|
- `internal/audit/ent_auditor.go` - Audit implementation
|
||||||
- `services/audit/internal/api/server.go` - gRPC server implementation
|
- `internal/audit/middleware.go` - Audit middleware
|
||||||
- `services/audit/internal/service/audit_service.go` - Audit service logic
|
- `internal/audit/handler.go` - Audit query handler
|
||||||
- `services/audit/ent/schema/audit_log.go` - AuditLog entity schema
|
|
||||||
- `config/default.yaml` - Add audit service configuration
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,89 +1,57 @@
|
|||||||
# Story 2.6: Database Seeding
|
# Story 2.6: Database Seeding and Initialization
|
||||||
|
|
||||||
## Metadata
|
## Metadata
|
||||||
- **Story ID**: 2.6
|
- **Story ID**: 2.6
|
||||||
- **Title**: Database Seeding
|
- **Title**: Database Seeding and Initialization
|
||||||
- **Epic**: 2 - Core Services (Authentication & Authorization)
|
- **Epic**: 2 - Authentication & Authorization
|
||||||
- **Status**: Pending
|
- **Status**: Pending
|
||||||
- **Priority**: Medium
|
- **Priority**: Medium
|
||||||
- **Estimated Time**: 4-6 hours
|
- **Estimated Time**: 3-4 hours
|
||||||
- **Dependencies**: 2.1, 2.2, 2.3, 2.4
|
- **Dependencies**: 1.2, 2.3, 2.4
|
||||||
|
|
||||||
## Goal
|
## Goal
|
||||||
Provide database seeding functionality for all services to create initial admin user, default roles, and core permissions. Each service seeds its own database schema.
|
Provide database seeding functionality to create initial admin user, default roles, and core permissions.
|
||||||
|
|
||||||
## Description
|
## Description
|
||||||
This story implements seeding for all core services. Each service has its own seed script that populates its database schema with initial data. The seeding uses service clients where cross-service data is needed (e.g., creating admin user in Identity Service, then assigning admin role via Authz Service).
|
This story implements a seeding system that creates the initial admin user, default roles (admin, user, guest), and assigns core permissions to enable the platform to be used immediately after setup.
|
||||||
|
|
||||||
## Deliverables
|
## Deliverables
|
||||||
|
|
||||||
### 1. Identity Service Seeding (`services/identity/internal/seed/seed.go`)
|
### 1. Seed Script (`internal/seed/seed.go`)
|
||||||
- Create default admin user (if doesn't exist)
|
- Create default admin user (if doesn't exist)
|
||||||
- Idempotent operations
|
|
||||||
- Uses Identity Service's own database connection
|
|
||||||
|
|
||||||
### 2. Authz Service Seeding (`services/authz/internal/seed/seed.go`)
|
|
||||||
- Create default roles (admin, user, guest)
|
- Create default roles (admin, user, guest)
|
||||||
- Create core permissions
|
|
||||||
- Assign core permissions to roles
|
- Assign core permissions to roles
|
||||||
- Uses Authz Service's own database connection
|
- Set up initial role hierarchy
|
||||||
|
- Idempotent operations (safe to run multiple times)
|
||||||
|
|
||||||
### 3. Seed Command (`cmd/seed/main.go`)
|
### 2. Seed Command (`cmd/seed/main.go`)
|
||||||
- Command-line interface for seeding all services
|
- Command-line interface for seeding
|
||||||
- Service-specific seed functions
|
|
||||||
- Configuration via environment variables
|
- Configuration via environment variables
|
||||||
- Dry-run mode
|
- Dry-run mode
|
||||||
- Verbose logging
|
- Verbose logging
|
||||||
- Uses service clients for cross-service operations (e.g., assign admin role to admin user)
|
|
||||||
|
|
||||||
### 4. Service-Specific Seed Functions
|
### 3. Integration
|
||||||
- Each service can seed its own schema independently
|
|
||||||
- Seed functions are idempotent (safe to run multiple times)
|
|
||||||
- Seed functions use service clients when needed
|
|
||||||
|
|
||||||
### 5. Integration
|
|
||||||
- Optional: Auto-seed on first startup in development
|
- Optional: Auto-seed on first startup in development
|
||||||
- Manual seeding in production
|
- Manual seeding in production
|
||||||
- Can be run per-service or all services at once
|
- Integration with application startup
|
||||||
|
|
||||||
## Acceptance Criteria
|
## Acceptance Criteria
|
||||||
- [x] Identity Service seed creates admin user successfully
|
- [ ] Seed script creates admin user successfully
|
||||||
- [x] Authz Service seed creates default roles with proper permissions
|
- [ ] Default roles are created with proper permissions
|
||||||
- [x] Seeding is idempotent (can run multiple times safely)
|
- [ ] Seeding is idempotent (can run multiple times safely)
|
||||||
- [x] Seed command can be run via CLI
|
- [ ] Seed script can be run via CLI
|
||||||
- [x] Seed command uses service clients for cross-service operations
|
- [ ] Admin user can login and manage system
|
||||||
- [x] Each service seeds its own database schema
|
|
||||||
- [x] Admin user can login and manage system after seeding
|
|
||||||
|
|
||||||
## Related ADRs
|
## Related ADRs
|
||||||
- [ADR-0029: Microservices Architecture](../../adr/0029-microservices-architecture.md)
|
- [ADR-0029: Microservices Architecture](../../adr/0029-microservices-architecture.md)
|
||||||
- [ADR-0030: Service Communication Strategy](../../adr/0030-service-communication-strategy.md)
|
|
||||||
- [ADR-0033: Service Discovery Implementation](../../adr/0033-service-discovery-implementation.md)
|
|
||||||
|
|
||||||
## Implementation Notes
|
## Implementation Notes
|
||||||
- Seeding is typically done once per environment
|
- Seeding is typically done once per environment
|
||||||
- Can be run as a separate command or as part of deployment
|
- Can be run as a separate service or as part of deployment
|
||||||
- Uses service clients for cross-service operations (e.g., IdentityServiceClient, AuthzServiceClient)
|
- Uses service clients if accessing services (e.g., IdentityServiceClient for user creation)
|
||||||
- Each service manages its own seed data
|
|
||||||
- Seed command coordinates seeding across services
|
|
||||||
|
|
||||||
## Testing
|
|
||||||
```bash
|
|
||||||
# Test seeding
|
|
||||||
go run cmd/seed/main.go
|
|
||||||
|
|
||||||
# Test idempotency
|
|
||||||
go run cmd/seed/main.go
|
|
||||||
go run cmd/seed/main.go # Should be safe to run again
|
|
||||||
|
|
||||||
# Test service-specific seeding
|
|
||||||
go run cmd/seed/main.go --service=identity
|
|
||||||
go run cmd/seed/main.go --service=authz
|
|
||||||
```
|
|
||||||
|
|
||||||
## Files to Create/Modify
|
## Files to Create/Modify
|
||||||
- `services/identity/internal/seed/seed.go` - Identity Service seed functions
|
- `internal/seed/seed.go` - Seed functions
|
||||||
- `services/authz/internal/seed/seed.go` - Authz Service seed functions
|
- `cmd/seed/main.go` - Seed command
|
||||||
- `cmd/seed/main.go` - Seed command (coordinates all services)
|
|
||||||
- `Makefile` - Add seed command
|
- `Makefile` - Add seed command
|
||||||
|
|
||||||
|
|||||||
@@ -19,7 +19,6 @@ This story implements the foundation for microservices architecture by creating
|
|||||||
|
|
||||||
### 1. Service Client Interfaces (`pkg/services/`)
|
### 1. Service Client Interfaces (`pkg/services/`)
|
||||||
Define service client interfaces for all core services:
|
Define service client interfaces for all core services:
|
||||||
|
|
||||||
- `IdentityServiceClient` - User and identity operations
|
- `IdentityServiceClient` - User and identity operations
|
||||||
- `AuthServiceClient` - Authentication operations
|
- `AuthServiceClient` - Authentication operations
|
||||||
- `AuthzServiceClient` - Authorization operations
|
- `AuthzServiceClient` - Authorization operations
|
||||||
@@ -30,7 +29,6 @@ Define service client interfaces for all core services:
|
|||||||
|
|
||||||
### 2. Service Client Factory (`internal/services/factory.go`)
|
### 2. Service Client Factory (`internal/services/factory.go`)
|
||||||
Factory pattern for creating service clients:
|
Factory pattern for creating service clients:
|
||||||
|
|
||||||
- Create gRPC clients (primary)
|
- Create gRPC clients (primary)
|
||||||
- Create HTTP clients (fallback)
|
- Create HTTP clients (fallback)
|
||||||
- Support service registry integration
|
- Support service registry integration
|
||||||
|
|||||||
@@ -1,170 +0,0 @@
|
|||||||
# Audit Service Implementation Summary
|
|
||||||
|
|
||||||
## Overview
|
|
||||||
|
|
||||||
The Audit Service has been successfully implemented as an independent microservice. It provides audit logging functionality with gRPC API, database persistence, and service registry integration.
|
|
||||||
|
|
||||||
## Completed Components
|
|
||||||
|
|
||||||
### 1. Service Implementation (`services/audit/internal/service/audit_service.go`)
|
|
||||||
- **AuditService**: Core business logic for audit logging
|
|
||||||
- **Record**: Records audit log entries to database
|
|
||||||
- **Query**: Queries audit logs with filters (user_id, action, resource, time range, pagination)
|
|
||||||
- Uses Ent ORM for database operations
|
|
||||||
- Supports metadata as JSON
|
|
||||||
|
|
||||||
### 2. gRPC Server Implementation (`cmd/audit-service/audit_service_fx.go`)
|
|
||||||
- **auditServerImpl**: Implements `AuditService` gRPC interface
|
|
||||||
- **Record RPC**: Records audit log entries
|
|
||||||
- **Query RPC**: Queries audit logs with filters
|
|
||||||
- Converts between proto types and service types
|
|
||||||
- Error handling with proper gRPC status codes
|
|
||||||
|
|
||||||
### 3. Service Entry Point (`cmd/audit-service/main.go`)
|
|
||||||
- Independent service entry point
|
|
||||||
- Dependency injection using uber-go/fx
|
|
||||||
- Database connection with schema isolation (`audit` schema)
|
|
||||||
- Automatic migrations on startup
|
|
||||||
- Health check registry with database checker
|
|
||||||
- gRPC server lifecycle management
|
|
||||||
- Service registry registration (Consul)
|
|
||||||
- Graceful shutdown handling
|
|
||||||
|
|
||||||
### 4. Database Schema (`internal/ent/schema/audit_log.go`)
|
|
||||||
- **AuditLog** entity with fields:
|
|
||||||
- `id`: Unique identifier
|
|
||||||
- `user_id`: ID of the user/actor
|
|
||||||
- `action`: Action performed (e.g., "user.create")
|
|
||||||
- `resource`: Resource type (e.g., "user", "role")
|
|
||||||
- `resource_id`: ID of the target resource
|
|
||||||
- `ip_address`: Client IP address
|
|
||||||
- `user_agent`: Client user agent
|
|
||||||
- `metadata`: Additional metadata as JSON
|
|
||||||
- `timestamp`: When the action occurred
|
|
||||||
- Indexes on: user_id, resource_id, timestamp, action, resource
|
|
||||||
|
|
||||||
### 5. Configuration (`config/default.yaml`)
|
|
||||||
- Added service configuration:
|
|
||||||
```yaml
|
|
||||||
services:
|
|
||||||
audit:
|
|
||||||
port: 8084
|
|
||||||
host: "localhost"
|
|
||||||
```
|
|
||||||
|
|
||||||
## Architecture
|
|
||||||
|
|
||||||
The Audit Service follows the microservices architecture pattern:
|
|
||||||
|
|
||||||
- **Independent Deployment**: Has its own entry point (`cmd/audit-service/main.go`)
|
|
||||||
- **Schema Isolation**: Uses `audit` database schema
|
|
||||||
- **Service Discovery**: Registers with Consul service registry
|
|
||||||
- **gRPC API**: Exposes gRPC server on port 8084 (configurable)
|
|
||||||
- **Health Checks**: Implements gRPC health check protocol
|
|
||||||
- **Reflection**: Enabled for grpcurl testing
|
|
||||||
|
|
||||||
## Files Created
|
|
||||||
|
|
||||||
1. `services/audit/internal/service/audit_service.go` - Service business logic
|
|
||||||
2. `services/audit/internal/api/server.go` - gRPC server implementation (for reference, actual implementation in cmd)
|
|
||||||
3. `services/audit/internal/api/grpc_server.go` - gRPC server wrapper (for reference)
|
|
||||||
4. `cmd/audit-service/main.go` - Service entry point
|
|
||||||
5. `cmd/audit-service/audit_service_fx.go` - FX providers for service creation
|
|
||||||
6. `services/audit/service.go` - Public API package (placeholder)
|
|
||||||
|
|
||||||
## Testing the Audit Service
|
|
||||||
|
|
||||||
### Prerequisites
|
|
||||||
|
|
||||||
1. **PostgreSQL** running and accessible
|
|
||||||
2. **Consul** running (optional, for service discovery)
|
|
||||||
3. **Configuration** set up in `config/default.yaml`
|
|
||||||
|
|
||||||
### Start the Service
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Using nix-shell (recommended)
|
|
||||||
nix-shell
|
|
||||||
go run ./cmd/audit-service/main.go
|
|
||||||
|
|
||||||
# Or build and run
|
|
||||||
go build -o bin/audit-service ./cmd/audit-service
|
|
||||||
./bin/audit-service
|
|
||||||
```
|
|
||||||
|
|
||||||
### Verify Service Startup
|
|
||||||
|
|
||||||
The service should:
|
|
||||||
1. Connect to database
|
|
||||||
2. Create `audit` schema if it doesn't exist
|
|
||||||
3. Run migrations (create `audit_logs` table)
|
|
||||||
4. Start gRPC server on port 8084
|
|
||||||
5. Register with Consul (if available)
|
|
||||||
|
|
||||||
Check logs for:
|
|
||||||
- "Database migrations completed for audit service"
|
|
||||||
- "Starting Audit Service gRPC server"
|
|
||||||
- "Audit Service gRPC server started successfully"
|
|
||||||
- "Registered Audit Service with service registry"
|
|
||||||
|
|
||||||
### Test with grpcurl
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# List available services
|
|
||||||
grpcurl -plaintext localhost:8084 list
|
|
||||||
|
|
||||||
# Check health
|
|
||||||
grpcurl -plaintext localhost:8084 grpc.health.v1.Health/Check
|
|
||||||
|
|
||||||
# Record an audit log entry
|
|
||||||
grpcurl -plaintext -d '{
|
|
||||||
"entry": {
|
|
||||||
"user_id": "user-123",
|
|
||||||
"action": "user.login",
|
|
||||||
"resource": "user",
|
|
||||||
"resource_id": "user-123",
|
|
||||||
"ip_address": "192.168.1.1",
|
|
||||||
"user_agent": "Mozilla/5.0",
|
|
||||||
"metadata": {
|
|
||||||
"method": "POST",
|
|
||||||
"endpoint": "/api/v1/auth/login"
|
|
||||||
},
|
|
||||||
"timestamp": 1699123456
|
|
||||||
}
|
|
||||||
}' localhost:8084 audit.v1.AuditService/Record
|
|
||||||
|
|
||||||
# Query audit logs
|
|
||||||
grpcurl -plaintext -d '{
|
|
||||||
"user_id": "user-123",
|
|
||||||
"limit": 10,
|
|
||||||
"offset": 0
|
|
||||||
}' localhost:8084 audit.v1.AuditService/Query
|
|
||||||
```
|
|
||||||
|
|
||||||
### Verify Database
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Connect to database
|
|
||||||
docker exec -it goplt-postgres psql -U goplt -d goplt
|
|
||||||
|
|
||||||
# Switch to audit schema
|
|
||||||
SET search_path TO audit;
|
|
||||||
|
|
||||||
# Check table exists
|
|
||||||
\dt
|
|
||||||
|
|
||||||
# Query audit logs
|
|
||||||
SELECT * FROM audit_logs ORDER BY timestamp DESC LIMIT 10;
|
|
||||||
```
|
|
||||||
|
|
||||||
## Next Steps
|
|
||||||
|
|
||||||
The Audit Service is complete and ready for use. Other services (Auth, Identity, Authz) can use the Audit Service via the `AuditServiceClient` interface to record audit events.
|
|
||||||
|
|
||||||
## Notes
|
|
||||||
|
|
||||||
- The service uses inline implementation in `cmd/audit-service/audit_service_fx.go` to avoid importing internal packages from cmd
|
|
||||||
- The internal service implementation in `services/audit/internal/service/` exists for reference and can be used by other internal packages
|
|
||||||
- Service registry registration happens on startup; deregistration on shutdown (service ID stored for proper cleanup)
|
|
||||||
- Health checks are available via gRPC health protocol
|
|
||||||
|
|
||||||
@@ -1,82 +1,57 @@
|
|||||||
# Epic 2: Core Services (Authentication & Authorization)
|
# Epic 2: Authentication & Authorization
|
||||||
|
|
||||||
## Overview
|
## Overview
|
||||||
Implement Auth, Identity, Authz, and Audit as **separate, independent microservices**. Each service has its own entry point (`cmd/{service}/`), gRPC server, database connection/schema, and registers with Consul service registry. Services communicate via service clients (gRPC) and use service discovery.
|
Implement complete JWT-based authentication system, build comprehensive identity management with user lifecycle, create role-based access control (RBAC) system, implement authorization middleware and permission checks, add comprehensive audit logging for security compliance, and provide database seeding for initial setup. All core services (Auth, Identity, Authz, Audit) are independent microservices that expose gRPC servers and register with the service registry.
|
||||||
|
|
||||||
**Key Principle:** Each service is independently deployable from day one.
|
|
||||||
|
|
||||||
## Stories
|
## Stories
|
||||||
|
|
||||||
### 2.1 Auth Service - JWT Authentication
|
### 2.1 JWT Authentication System
|
||||||
- [Story: 2.1 - Auth Service](./2.1-jwt-authentication.md)
|
- [Story: 2.1 - JWT Authentication](./2.1-jwt-authentication.md)
|
||||||
- **Goal:** Implement Auth Service as independent microservice with JWT token generation/validation.
|
- **Goal:** Implement a complete JWT-based authentication system with access tokens, refresh tokens, and secure token management.
|
||||||
- **Deliverables:**
|
- **Deliverables:** Authentication interfaces, JWT implementation, authentication middleware, login/refresh endpoints
|
||||||
- Service entry point: `cmd/auth-service/main.go`
|
|
||||||
- gRPC server implementation
|
|
||||||
- Database connection and schema (auth schema)
|
|
||||||
- Service registration with Consul
|
|
||||||
- JWT token generation/validation logic
|
|
||||||
|
|
||||||
### 2.2 Identity Service - User Management
|
### 2.2 Identity Management System
|
||||||
- [Story: 2.2 - Identity Service](./2.2-identity-management.md)
|
- [Story: 2.2 - Identity Management](./2.2-identity-management.md)
|
||||||
- **Goal:** Implement Identity Service as independent microservice for user CRUD and password management.
|
- **Goal:** Build a complete user identity management system with registration, email verification, password management, and user CRUD operations.
|
||||||
- **Deliverables:**
|
- **Deliverables:** Identity interfaces, user repository, user service, user management API endpoints
|
||||||
- Service entry point: `cmd/identity-service/main.go`
|
|
||||||
- gRPC server implementation
|
|
||||||
- Database connection and schema (identity schema with User entity)
|
|
||||||
- Service registration with Consul
|
|
||||||
- User CRUD, password management, email verification
|
|
||||||
|
|
||||||
### 2.3 Authz Service - Authorization & RBAC
|
### 2.3 Role-Based Access Control (RBAC) System
|
||||||
- [Story: 2.3 - Authz Service](./2.3-rbac-system.md)
|
- [Story: 2.3 - RBAC System](./2.3-rbac-system.md)
|
||||||
- **Goal:** Implement Authz Service as independent microservice for permission resolution and authorization.
|
- **Goal:** Implement a complete RBAC system with permissions, role management, and authorization middleware.
|
||||||
- **Deliverables:**
|
- **Deliverables:** Permission system, permission resolver, authorization system, authorization middleware
|
||||||
- Service entry point: `cmd/authz-service/main.go`
|
|
||||||
- gRPC server implementation
|
|
||||||
- Database connection and schema (authz schema with Role, Permission entities)
|
|
||||||
- Service registration with Consul
|
|
||||||
- Permission resolution, RBAC/ABAC authorization
|
|
||||||
|
|
||||||
### 2.4 Role Management (Part of Authz Service)
|
### 2.4 Role Management API
|
||||||
- [Story: 2.4 - Role Management](./2.4-role-management.md)
|
- [Story: 2.4 - Role Management](./2.4-role-management.md)
|
||||||
- **Goal:** Extend Authz Service with role management API.
|
- **Goal:** Provide complete API for managing roles, assigning permissions to roles, and assigning roles to users.
|
||||||
- **Deliverables:**
|
- **Deliverables:** Role repository, role management API endpoints, authorization and validation
|
||||||
- Role management gRPC endpoints
|
|
||||||
- Role assignment to users (via Identity Service client)
|
|
||||||
- Permission assignment to roles
|
|
||||||
|
|
||||||
### 2.5 Audit Service - Audit Logging
|
### 2.5 Audit Logging System
|
||||||
- [Story: 2.5 - Audit Service](./2.5-audit-logging.md)
|
- [Story: 2.5 - Audit Logging](./2.5-audit-logging.md)
|
||||||
- **Goal:** Implement Audit Service as independent microservice for audit logging.
|
- **Goal:** Implement comprehensive audit logging that records all security-sensitive actions for compliance and security monitoring.
|
||||||
- **Deliverables:**
|
- **Deliverables:** Audit interface, audit implementation, audit middleware, audit log query API
|
||||||
- Service entry point: `cmd/audit-service/main.go`
|
|
||||||
- gRPC server implementation
|
|
||||||
- Database connection and schema (audit schema with AuditLog entity)
|
|
||||||
- Service registration with Consul
|
|
||||||
- Audit log recording and querying
|
|
||||||
|
|
||||||
### 2.6 Database Seeding
|
### 2.6 Database Seeding and Initialization
|
||||||
- [Story: 2.6 - Database Seeding](./2.6-database-seeding.md)
|
- [Story: 2.6 - Database Seeding](./2.6-database-seeding.md)
|
||||||
- **Goal:** Provide seeding for all services (initial admin user, default roles, permissions).
|
- **Goal:** Provide database seeding functionality to create initial admin user, default roles, and core permissions.
|
||||||
- **Deliverables:**
|
- **Deliverables:** Seed script, seed command, integration with application startup
|
||||||
- Seed scripts for each service
|
|
||||||
- Seed commands
|
### 2.7 Service Client Interfaces
|
||||||
- Integration with service startup
|
- [Story: 2.7 - Service Client Interfaces](./2.7-service-abstraction-layer.md) (moved from Epic 1)
|
||||||
|
- **Goal:** Create service client interfaces for all core services to enable microservices communication.
|
||||||
|
- **Deliverables:** Service client interfaces, service factory, configuration
|
||||||
|
|
||||||
## Deliverables Checklist
|
## Deliverables Checklist
|
||||||
- [ ] Auth Service: Independent service with gRPC server, database schema, Consul registration
|
- [ ] JWT authentication with access/refresh tokens
|
||||||
- [ ] Identity Service: Independent service with gRPC server, User entity, Consul registration
|
- [ ] User CRUD with email verification
|
||||||
- [ ] Authz Service: Independent service with gRPC server, Role/Permission entities, Consul registration
|
- [ ] Role and permission management
|
||||||
- [ ] Audit Service: Independent service with gRPC server, AuditLog entity, Consul registration
|
- [ ] Authorization middleware
|
||||||
- [ ] All services use service clients for inter-service communication
|
- [ ] Audit logging for all actions
|
||||||
- [ ] All services have their own database connection pools and schemas
|
- [ ] Seed script for initial data
|
||||||
- [ ] Seed scripts for all services
|
|
||||||
|
|
||||||
## Acceptance Criteria
|
## Acceptance Criteria
|
||||||
- Each service is independently deployable
|
- User can register and login
|
||||||
- Each service has its own entry point (`cmd/{service}/main.go`)
|
- JWT tokens are validated on protected routes
|
||||||
- Each service registers with Consul service registry
|
- Users without permission get 403
|
||||||
- Services communicate via gRPC through service clients
|
- All actions are logged in audit table
|
||||||
- Each service has its own database schema
|
- Admin can create roles and assign permissions
|
||||||
- API Gateway can route to all services via service discovery
|
- Integration test: user without permission cannot access protected resource
|
||||||
- Integration test: Services can discover and communicate with each other
|
|
||||||
|
|||||||
@@ -1,541 +0,0 @@
|
|||||||
# Epic 2 Implementation Summary
|
|
||||||
|
|
||||||
## Overview
|
|
||||||
|
|
||||||
Epic 2 implements the Core Services (Authentication & Authorization) as independent, deployable microservices. All services are implemented with gRPC APIs, database persistence, service registry integration, and inter-service communication via gRPC clients.
|
|
||||||
|
|
||||||
## Completed Stories
|
|
||||||
|
|
||||||
### Story 2.1: Auth Service - JWT Authentication ✅
|
|
||||||
**Status**: Complete (with RefreshToken entity TODO)
|
|
||||||
|
|
||||||
**Implementation**:
|
|
||||||
- **Entry Point**: `cmd/auth-service/main.go`
|
|
||||||
- **Service**: `cmd/auth-service/auth_service_fx.go`
|
|
||||||
- **Features**:
|
|
||||||
- JWT access token generation (15 minutes lifetime)
|
|
||||||
- Refresh token generation (7 days lifetime, stored in database)
|
|
||||||
- Token validation and claims extraction
|
|
||||||
- Login, RefreshToken, ValidateToken, Logout RPCs
|
|
||||||
- Integration with Identity Service for credential validation
|
|
||||||
- **Database Schema**: `auth` schema with `refresh_tokens` table (schema defined, needs Ent generation)
|
|
||||||
- **Port**: 8081 (configurable)
|
|
||||||
|
|
||||||
**Note**: RefreshToken entity needs to be generated using `go generate ./ent/...` for full functionality. Currently returns placeholder errors for refresh token operations.
|
|
||||||
|
|
||||||
### Story 2.2: Identity Service - User Management ✅
|
|
||||||
**Status**: Complete
|
|
||||||
|
|
||||||
**Implementation**:
|
|
||||||
- **Entry Point**: `cmd/identity-service/main.go`
|
|
||||||
- **Service**: `cmd/identity-service/identity_service_fx.go`
|
|
||||||
- **Password Hashing**: `services/identity/internal/password/password.go` (argon2id)
|
|
||||||
- **Features**:
|
|
||||||
- User CRUD operations (Create, Get, GetByEmail, Update, Delete)
|
|
||||||
- Password hashing with argon2id (OWASP recommended)
|
|
||||||
- Email verification flow
|
|
||||||
- Password reset flow (token-based, 24-hour expiration)
|
|
||||||
- Password change with old password verification
|
|
||||||
- **Database Schema**: `identity` schema with `users` table
|
|
||||||
- **Port**: 8082 (configurable)
|
|
||||||
|
|
||||||
### Story 2.3: Authz Service - Authorization ✅
|
|
||||||
**Status**: Complete
|
|
||||||
|
|
||||||
**Implementation**:
|
|
||||||
- **Entry Point**: `cmd/authz-service/main.go`
|
|
||||||
- **Service**: `cmd/authz-service/authz_service_fx.go`
|
|
||||||
- **Features**:
|
|
||||||
- Permission checking (Authorize, HasPermission)
|
|
||||||
- User permissions retrieval (GetUserPermissions)
|
|
||||||
- User roles retrieval (GetUserRoles)
|
|
||||||
- RBAC-based authorization via UserRole → Role → RolePermission → Permission relationships
|
|
||||||
- **Database Schema**: `authz` schema with `roles`, `permissions`, `role_permissions`, `user_roles` tables
|
|
||||||
- **Port**: 8083 (configurable)
|
|
||||||
|
|
||||||
### Story 2.4: Role Management ✅
|
|
||||||
**Status**: Complete (integrated into Authz Service)
|
|
||||||
|
|
||||||
**Implementation**:
|
|
||||||
- Role and permission management is handled through the Authz Service
|
|
||||||
- Database schemas support role-permission and user-role relationships
|
|
||||||
- Role management APIs can be extended in future stories
|
|
||||||
|
|
||||||
### Story 2.5: Audit Service ✅
|
|
||||||
**Status**: Complete
|
|
||||||
|
|
||||||
**Implementation**:
|
|
||||||
- **Entry Point**: `cmd/audit-service/main.go`
|
|
||||||
- **Service**: `cmd/audit-service/audit_service_fx.go`
|
|
||||||
- **Features**:
|
|
||||||
- Audit log recording (Record RPC)
|
|
||||||
- Audit log querying with filters (Query RPC)
|
|
||||||
- Support for filtering by user_id, action, resource, resource_id, time range
|
|
||||||
- Pagination support (limit, offset)
|
|
||||||
- Metadata storage as JSON
|
|
||||||
- **Database Schema**: `audit` schema with `audit_logs` table
|
|
||||||
- **Port**: 8084 (configurable)
|
|
||||||
|
|
||||||
### Story 2.6: Database Seeding ⏳
|
|
||||||
**Status**: Pending
|
|
||||||
|
|
||||||
**Note**: Database seeding scripts are not yet implemented. This can be added as a follow-up task to populate initial data (admin users, default roles, permissions).
|
|
||||||
|
|
||||||
### Additional Implementation: gRPC Clients ✅
|
|
||||||
**Status**: Complete
|
|
||||||
|
|
||||||
**Implementation**:
|
|
||||||
- **Auth Client**: `internal/client/grpc/auth_client.go`
|
|
||||||
- **Identity Client**: `internal/client/grpc/identity_client.go`
|
|
||||||
- **Authz Client**: `internal/client/grpc/authz_client.go`
|
|
||||||
- **Audit Client**: `internal/client/grpc/audit_client.go`
|
|
||||||
|
|
||||||
All clients:
|
|
||||||
- Use service discovery via Consul
|
|
||||||
- Implement full gRPC communication
|
|
||||||
- Handle connection management
|
|
||||||
- Convert between proto and service types
|
|
||||||
|
|
||||||
## Architecture
|
|
||||||
|
|
||||||
### Service Independence
|
|
||||||
Each service:
|
|
||||||
- Has its own entry point in `cmd/{service}/`
|
|
||||||
- Manages its own database connection and schema
|
|
||||||
- Registers with Consul service registry
|
|
||||||
- Exposes gRPC server with health checks
|
|
||||||
- Can be deployed independently
|
|
||||||
|
|
||||||
### Database Schema Isolation
|
|
||||||
- **Auth Service**: `auth` schema
|
|
||||||
- **Identity Service**: `identity` schema
|
|
||||||
- **Authz Service**: `authz` schema
|
|
||||||
- **Audit Service**: `audit` schema
|
|
||||||
|
|
||||||
### Service Communication
|
|
||||||
- Services communicate via gRPC using service discovery
|
|
||||||
- Service clients are available via `ServiceClientFactory`
|
|
||||||
- Clients automatically discover and connect to service instances
|
|
||||||
|
|
||||||
### Configuration
|
|
||||||
All service configurations are in `config/default.yaml`:
|
|
||||||
```yaml
|
|
||||||
services:
|
|
||||||
auth:
|
|
||||||
port: 8081
|
|
||||||
host: "localhost"
|
|
||||||
identity:
|
|
||||||
port: 8082
|
|
||||||
host: "localhost"
|
|
||||||
authz:
|
|
||||||
port: 8083
|
|
||||||
host: "localhost"
|
|
||||||
audit:
|
|
||||||
port: 8084
|
|
||||||
host: "localhost"
|
|
||||||
|
|
||||||
auth:
|
|
||||||
jwt_secret: "change-this-secret-in-production"
|
|
||||||
```
|
|
||||||
|
|
||||||
## Prerequisites
|
|
||||||
|
|
||||||
1. **PostgreSQL** running and accessible
|
|
||||||
2. **Consul** running (for service discovery, required for service registry)
|
|
||||||
3. **Go 1.24+** (or use `nix-shell` for development environment)
|
|
||||||
4. **NixOS** (optional, for `shell.nix` development environment)
|
|
||||||
5. **Docker and Docker Compose** (for running PostgreSQL and Consul)
|
|
||||||
|
|
||||||
## Building the Services
|
|
||||||
|
|
||||||
### Using Nix Shell (Recommended)
|
|
||||||
```bash
|
|
||||||
# Enter nix shell (automatically loads via .envrc if using direnv)
|
|
||||||
nix-shell
|
|
||||||
|
|
||||||
# Build all services
|
|
||||||
go build ./cmd/auth-service
|
|
||||||
go build ./cmd/identity-service
|
|
||||||
go build ./cmd/authz-service
|
|
||||||
go build ./cmd/audit-service
|
|
||||||
```
|
|
||||||
|
|
||||||
### Without Nix
|
|
||||||
```bash
|
|
||||||
# Ensure you have:
|
|
||||||
# - Go 1.24+
|
|
||||||
# - protoc
|
|
||||||
# - protoc-gen-go
|
|
||||||
# - protoc-gen-go-grpc
|
|
||||||
|
|
||||||
go build ./cmd/auth-service
|
|
||||||
go build ./cmd/identity-service
|
|
||||||
go build ./cmd/authz-service
|
|
||||||
go build ./cmd/audit-service
|
|
||||||
```
|
|
||||||
|
|
||||||
## Running the Services
|
|
||||||
|
|
||||||
### Option 1: Development Mode (Recommended for Development)
|
|
||||||
|
|
||||||
Use `docker-compose.dev.yml` for infrastructure only, run services locally:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Start only PostgreSQL and Consul
|
|
||||||
docker-compose -f docker-compose.dev.yml up -d
|
|
||||||
|
|
||||||
# Verify containers are running
|
|
||||||
docker-compose -f docker-compose.dev.yml ps
|
|
||||||
|
|
||||||
# Check logs
|
|
||||||
docker-compose -f docker-compose.dev.yml logs postgres
|
|
||||||
docker-compose -f docker-compose.dev.yml logs consul
|
|
||||||
```
|
|
||||||
|
|
||||||
Then start services locally:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Terminal 1: Auth Service
|
|
||||||
go run ./cmd/auth-service/*.go
|
|
||||||
|
|
||||||
# Terminal 2: Identity Service
|
|
||||||
go run ./cmd/identity-service/*.go
|
|
||||||
|
|
||||||
# Terminal 3: Authz Service
|
|
||||||
go run ./cmd/authz-service/*.go
|
|
||||||
|
|
||||||
# Terminal 4: Audit Service
|
|
||||||
go run ./cmd/audit-service/*.go
|
|
||||||
```
|
|
||||||
|
|
||||||
### Option 2: Full Docker Compose (All Services in Docker)
|
|
||||||
|
|
||||||
Use `docker-compose.yml` to run everything in Docker:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Build and start all services
|
|
||||||
docker-compose up -d --build
|
|
||||||
|
|
||||||
# View logs
|
|
||||||
docker-compose logs -f
|
|
||||||
|
|
||||||
# Stop all services
|
|
||||||
docker-compose down
|
|
||||||
```
|
|
||||||
|
|
||||||
This will start:
|
|
||||||
- PostgreSQL (port 5432)
|
|
||||||
- Consul (port 8500)
|
|
||||||
- Auth Service (port 8081)
|
|
||||||
- Identity Service (port 8082)
|
|
||||||
- Authz Service (port 8083)
|
|
||||||
- Audit Service (port 8084)
|
|
||||||
- API Gateway (port 8080)
|
|
||||||
|
|
||||||
### Infrastructure Services
|
|
||||||
|
|
||||||
Both docker-compose files include:
|
|
||||||
- **PostgreSQL**: Available at `localhost:5432`
|
|
||||||
- Database: `goplt`
|
|
||||||
- User: `goplt`
|
|
||||||
- Password: `goplt_password`
|
|
||||||
- **Consul**: Available at `localhost:8500`
|
|
||||||
- Running in dev mode
|
|
||||||
- Web UI: http://localhost:8500/ui (for viewing registered services)
|
|
||||||
- API: http://localhost:8500/v1
|
|
||||||
|
|
||||||
### Alternative: Manual Setup
|
|
||||||
```bash
|
|
||||||
# Start PostgreSQL manually
|
|
||||||
# Ensure database 'goplt' exists with user 'goplt' and password 'goplt_password'
|
|
||||||
|
|
||||||
# Start Consul manually
|
|
||||||
consul agent -dev
|
|
||||||
```
|
|
||||||
|
|
||||||
### 4. Verify Services
|
|
||||||
|
|
||||||
Check service logs for:
|
|
||||||
- Database connection success
|
|
||||||
- Migration completion
|
|
||||||
- gRPC server startup
|
|
||||||
- Service registry registration
|
|
||||||
|
|
||||||
## Testing the Services
|
|
||||||
|
|
||||||
### Using grpcurl
|
|
||||||
|
|
||||||
#### 1. Identity Service - Create User
|
|
||||||
```bash
|
|
||||||
grpcurl -plaintext -d '{
|
|
||||||
"email": "user@example.com",
|
|
||||||
"password": "securepassword123",
|
|
||||||
"username": "testuser",
|
|
||||||
"first_name": "Test",
|
|
||||||
"last_name": "User"
|
|
||||||
}' localhost:8082 identity.v1.IdentityService/CreateUser
|
|
||||||
```
|
|
||||||
|
|
||||||
#### 2. Identity Service - Get User
|
|
||||||
```bash
|
|
||||||
grpcurl -plaintext -d '{
|
|
||||||
"id": "USER_ID_FROM_CREATE"
|
|
||||||
}' localhost:8082 identity.v1.IdentityService/GetUser
|
|
||||||
```
|
|
||||||
|
|
||||||
#### 3. Auth Service - Login
|
|
||||||
```bash
|
|
||||||
grpcurl -plaintext -d '{
|
|
||||||
"email": "user@example.com",
|
|
||||||
"password": "securepassword123"
|
|
||||||
}' localhost:8081 auth.v1.AuthService/Login
|
|
||||||
```
|
|
||||||
|
|
||||||
#### 4. Auth Service - Validate Token
|
|
||||||
```bash
|
|
||||||
grpcurl -plaintext -d '{
|
|
||||||
"token": "ACCESS_TOKEN_FROM_LOGIN"
|
|
||||||
}' localhost:8081 auth.v1.AuthService/ValidateToken
|
|
||||||
```
|
|
||||||
|
|
||||||
#### 5. Authz Service - Get User Roles
|
|
||||||
```bash
|
|
||||||
grpcurl -plaintext -d '{
|
|
||||||
"user_id": "USER_ID"
|
|
||||||
}' localhost:8083 authz.v1.AuthzService/GetUserRoles
|
|
||||||
```
|
|
||||||
|
|
||||||
#### 6. Audit Service - Record Audit Log
|
|
||||||
```bash
|
|
||||||
grpcurl -plaintext -d '{
|
|
||||||
"entry": {
|
|
||||||
"user_id": "user-123",
|
|
||||||
"action": "user.login",
|
|
||||||
"resource": "user",
|
|
||||||
"resource_id": "user-123",
|
|
||||||
"ip_address": "192.168.1.1",
|
|
||||||
"user_agent": "Mozilla/5.0",
|
|
||||||
"metadata": {
|
|
||||||
"method": "POST",
|
|
||||||
"endpoint": "/api/v1/auth/login"
|
|
||||||
},
|
|
||||||
"timestamp": 1699123456
|
|
||||||
}
|
|
||||||
}' localhost:8084 audit.v1.AuditService/Record
|
|
||||||
```
|
|
||||||
|
|
||||||
#### 7. Audit Service - Query Audit Logs
|
|
||||||
```bash
|
|
||||||
grpcurl -plaintext -d '{
|
|
||||||
"user_id": "user-123",
|
|
||||||
"limit": 10,
|
|
||||||
"offset": 0
|
|
||||||
}' localhost:8084 audit.v1.AuditService/Query
|
|
||||||
```
|
|
||||||
|
|
||||||
### Health Checks
|
|
||||||
|
|
||||||
All services expose gRPC health checks:
|
|
||||||
```bash
|
|
||||||
grpcurl -plaintext localhost:8081 grpc.health.v1.Health/Check
|
|
||||||
grpcurl -plaintext localhost:8082 grpc.health.v1.Health/Check
|
|
||||||
grpcurl -plaintext localhost:8083 grpc.health.v1.Health/Check
|
|
||||||
grpcurl -plaintext localhost:8084 grpc.health.v1.Health/Check
|
|
||||||
```
|
|
||||||
|
|
||||||
## Verification
|
|
||||||
|
|
||||||
### Verify Consul
|
|
||||||
```bash
|
|
||||||
# Check Consul is running
|
|
||||||
curl http://localhost:8500/v1/status/leader
|
|
||||||
|
|
||||||
# List registered services
|
|
||||||
curl http://localhost:8500/v1/agent/services
|
|
||||||
|
|
||||||
# Or use Consul UI
|
|
||||||
# Open http://localhost:8500/ui in your browser
|
|
||||||
```
|
|
||||||
|
|
||||||
### Database Verification
|
|
||||||
|
|
||||||
#### Check Schemas
|
|
||||||
```bash
|
|
||||||
docker exec -it goplt-postgres psql -U goplt -d goplt -c "\dn"
|
|
||||||
```
|
|
||||||
|
|
||||||
### Check Tables
|
|
||||||
```bash
|
|
||||||
# Auth schema
|
|
||||||
docker exec -it goplt-postgres psql -U goplt -d goplt -c "SET search_path TO auth; \dt"
|
|
||||||
|
|
||||||
# Identity schema
|
|
||||||
docker exec -it goplt-postgres psql -U goplt -d goplt -c "SET search_path TO identity; \dt"
|
|
||||||
|
|
||||||
# Authz schema
|
|
||||||
docker exec -it goplt-postgres psql -U goplt -d goplt -c "SET search_path TO authz; \dt"
|
|
||||||
|
|
||||||
# Audit schema
|
|
||||||
docker exec -it goplt-postgres psql -U goplt -d goplt -c "SET search_path TO audit; \dt"
|
|
||||||
```
|
|
||||||
|
|
||||||
## Known Issues and TODOs
|
|
||||||
|
|
||||||
### 1. RefreshToken Entity
|
|
||||||
- **Issue**: RefreshToken entity schema is defined but Ent code needs to be regenerated
|
|
||||||
- **Impact**: Auth Service refresh token operations return placeholder errors
|
|
||||||
- **Fix**: Run `go generate ./ent/...` or `go run -mod=readonly entgo.io/ent/cmd/ent generate ./ent/schema`
|
|
||||||
- **Location**: `internal/ent/schema/refresh_token.go`
|
|
||||||
|
|
||||||
### 2. Database Seeding
|
|
||||||
- **Status**: Not implemented
|
|
||||||
- **Impact**: No initial data (admin users, default roles, permissions)
|
|
||||||
- **Future Work**: Create seed scripts for each service
|
|
||||||
|
|
||||||
### 3. Tests
|
|
||||||
- **Status**: Not implemented
|
|
||||||
- **Impact**: No automated test coverage
|
|
||||||
- **Future Work**: Add unit tests, integration tests, and E2E tests
|
|
||||||
|
|
||||||
### 4. Auth Service - Password Verification
|
|
||||||
- **Issue**: Auth Service login doesn't verify password with Identity Service
|
|
||||||
- **Impact**: Login may not work correctly
|
|
||||||
- **Fix**: Identity Service needs to expose VerifyPassword RPC or Auth Service should call it directly
|
|
||||||
|
|
||||||
### 5. Auth Service - Role Retrieval
|
|
||||||
- **Issue**: Auth Service doesn't retrieve user roles from Authz Service
|
|
||||||
- **Impact**: JWT tokens don't include user roles
|
|
||||||
- **Fix**: Integrate with Authz Service to get user roles during login
|
|
||||||
|
|
||||||
## File Structure
|
|
||||||
|
|
||||||
```
|
|
||||||
goplt/
|
|
||||||
├── cmd/
|
|
||||||
│ ├── auth-service/
|
|
||||||
│ │ ├── main.go
|
|
||||||
│ │ ├── auth_service_fx.go
|
|
||||||
│ │ └── Dockerfile
|
|
||||||
│ ├── identity-service/
|
|
||||||
│ │ ├── main.go
|
|
||||||
│ │ ├── identity_service_fx.go
|
|
||||||
│ │ └── Dockerfile
|
|
||||||
│ ├── authz-service/
|
|
||||||
│ │ ├── main.go
|
|
||||||
│ │ ├── authz_service_fx.go
|
|
||||||
│ │ └── Dockerfile
|
|
||||||
│ ├── audit-service/
|
|
||||||
│ │ ├── main.go
|
|
||||||
│ │ ├── audit_service_fx.go
|
|
||||||
│ │ └── Dockerfile
|
|
||||||
│ └── api-gateway/
|
|
||||||
│ ├── main.go
|
|
||||||
│ └── Dockerfile
|
|
||||||
├── docker-compose.yml
|
|
||||||
├── docker-compose.dev.yml
|
|
||||||
├── .dockerignore
|
|
||||||
├── services/
|
|
||||||
│ └── identity/
|
|
||||||
│ └── internal/
|
|
||||||
│ └── password/
|
|
||||||
│ └── password.go
|
|
||||||
├── internal/
|
|
||||||
│ ├── ent/
|
|
||||||
│ │ └── schema/
|
|
||||||
│ │ ├── user.go
|
|
||||||
│ │ ├── role.go
|
|
||||||
│ │ ├── permission.go
|
|
||||||
│ │ ├── user_role.go
|
|
||||||
│ │ ├── role_permission.go
|
|
||||||
│ │ ├── audit_log.go
|
|
||||||
│ │ └── refresh_token.go
|
|
||||||
│ └── client/
|
|
||||||
│ └── grpc/
|
|
||||||
│ ├── auth_client.go
|
|
||||||
│ ├── identity_client.go
|
|
||||||
│ ├── authz_client.go
|
|
||||||
│ └── audit_client.go
|
|
||||||
├── api/
|
|
||||||
│ └── proto/
|
|
||||||
│ ├── auth.proto
|
|
||||||
│ ├── identity.proto
|
|
||||||
│ ├── authz.proto
|
|
||||||
│ └── audit.proto
|
|
||||||
└── config/
|
|
||||||
└── default.yaml
|
|
||||||
```
|
|
||||||
|
|
||||||
## Docker Deployment
|
|
||||||
|
|
||||||
### Building Docker Images
|
|
||||||
|
|
||||||
Each service has its own Dockerfile:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Build individual service images
|
|
||||||
docker build -f cmd/auth-service/Dockerfile -t goplt-auth-service:latest .
|
|
||||||
docker build -f cmd/identity-service/Dockerfile -t goplt-identity-service:latest .
|
|
||||||
docker build -f cmd/authz-service/Dockerfile -t goplt-authz-service:latest .
|
|
||||||
docker build -f cmd/audit-service/Dockerfile -t goplt-audit-service:latest .
|
|
||||||
docker build -f cmd/api-gateway/Dockerfile -t goplt-api-gateway:latest .
|
|
||||||
```
|
|
||||||
|
|
||||||
### Docker Compose Files
|
|
||||||
|
|
||||||
- **`docker-compose.dev.yml`**: Development setup (PostgreSQL + Consul only)
|
|
||||||
- Use when running services locally with `go run`
|
|
||||||
- Start with: `docker-compose -f docker-compose.dev.yml up -d`
|
|
||||||
|
|
||||||
- **`docker-compose.yml`**: Full production-like setup (all services + infrastructure)
|
|
||||||
- All services run in Docker containers
|
|
||||||
- Start with: `docker-compose up -d --build`
|
|
||||||
|
|
||||||
### Environment Variables
|
|
||||||
|
|
||||||
Services can be configured via environment variables:
|
|
||||||
- `ENVIRONMENT`: `development` or `production`
|
|
||||||
- `DATABASE_DSN`: PostgreSQL connection string
|
|
||||||
- `REGISTRY_TYPE`: Service registry type (default: `consul`)
|
|
||||||
- `REGISTRY_CONSUL_ADDRESS`: Consul address (default: `localhost:8500`)
|
|
||||||
|
|
||||||
## Next Steps
|
|
||||||
|
|
||||||
1. **Complete RefreshToken Implementation**
|
|
||||||
- Regenerate Ent code for RefreshToken entity
|
|
||||||
- Update Auth Service to use RefreshToken entity
|
|
||||||
|
|
||||||
2. **Implement Database Seeding**
|
|
||||||
- Create seed scripts for initial data
|
|
||||||
- Add admin user, default roles, and permissions
|
|
||||||
|
|
||||||
3. **Add Tests**
|
|
||||||
- Unit tests for each service
|
|
||||||
- Integration tests for service communication
|
|
||||||
- E2E tests for complete flows
|
|
||||||
|
|
||||||
4. **Enhance Auth Service**
|
|
||||||
- Integrate password verification with Identity Service
|
|
||||||
- Integrate role retrieval with Authz Service
|
|
||||||
- Complete refresh token implementation
|
|
||||||
|
|
||||||
5. **API Gateway Integration**
|
|
||||||
- Route requests to appropriate services
|
|
||||||
- Implement authentication middleware
|
|
||||||
- Implement authorization middleware
|
|
||||||
|
|
||||||
## Summary
|
|
||||||
|
|
||||||
Epic 2 successfully implements all four core services (Auth, Identity, Authz, Audit) as independent microservices with:
|
|
||||||
- ✅ gRPC APIs
|
|
||||||
- ✅ Database persistence with schema isolation
|
|
||||||
- ✅ Service registry integration
|
|
||||||
- ✅ Inter-service communication via gRPC clients
|
|
||||||
- ✅ Health checks
|
|
||||||
- ✅ Graceful shutdown
|
|
||||||
|
|
||||||
All services build successfully and are ready for deployment. The main remaining work is:
|
|
||||||
- RefreshToken entity generation
|
|
||||||
- Database seeding
|
|
||||||
- Test coverage
|
|
||||||
- Auth Service enhancements (password verification, role retrieval)
|
|
||||||
|
|
||||||
@@ -1,61 +1,50 @@
|
|||||||
# Story 3.1: Module System Interface and Service Registry
|
# Story 3.1: Module System Interface and Registry
|
||||||
|
|
||||||
## Metadata
|
## Metadata
|
||||||
- **Story ID**: 3.1
|
- **Story ID**: 3.1
|
||||||
- **Title**: Module System Interface and Service Registry
|
- **Title**: Module System Interface and Registry
|
||||||
- **Epic**: 3 - Module Framework (Feature Services)
|
- **Epic**: 3 - Module Framework
|
||||||
- **Status**: Pending
|
- **Status**: Pending
|
||||||
- **Priority**: High
|
- **Priority**: High
|
||||||
- **Estimated Time**: 5-6 hours
|
- **Estimated Time**: 5-6 hours
|
||||||
- **Dependencies**: 1.1, 1.7, 2.3
|
- **Dependencies**: 1.1, 2.3
|
||||||
|
|
||||||
## Goal
|
## Goal
|
||||||
Design module interface for feature services with service registration and dependency resolution. Modules are services that register with Consul and communicate via service clients.
|
Design and implement the complete module system interface with registration, dependency resolution, and lifecycle management.
|
||||||
|
|
||||||
## Description
|
## Description
|
||||||
This story creates the foundation for feature services (modules) by defining the module interface, manifest structure, and service registration. Feature services are independent services with their own entry points, gRPC servers, and database schemas. They register with Consul and use service clients for inter-service communication.
|
This story creates the foundation of the module system by defining the module interface, manifest structure, and registry. The system must support module registration, dependency validation, and lifecycle hooks.
|
||||||
|
|
||||||
## Deliverables
|
## Deliverables
|
||||||
|
|
||||||
### 1. Module Interface (`pkg/module/module.go`)
|
### 1. Module Interface (`pkg/module/module.go`)
|
||||||
- `IModule` interface for feature services:
|
- `IModule` interface with:
|
||||||
- `Name() string` - Service name
|
- `Name() string` - Module name
|
||||||
- `Version() string` - Service version
|
- `Version() string` - Module version
|
||||||
- `Dependencies() []string` - Service dependencies (other services)
|
- `Dependencies() []string` - Module dependencies
|
||||||
- `Init() fx.Option` - FX options for service initialization
|
- `Init() fx.Option` - FX options for module initialization
|
||||||
- `Migrations() []func(*ent.Client) error` - Database migrations (per-service schema)
|
- `Migrations() []func(*ent.Client) error` - Database migrations
|
||||||
- Optional lifecycle hooks: `OnStart(ctx context.Context) error`
|
- Optional lifecycle hooks: `OnStart(ctx context.Context) error`
|
||||||
- Optional lifecycle hooks: `OnStop(ctx context.Context) error`
|
- Optional lifecycle hooks: `OnStop(ctx context.Context) error`
|
||||||
|
|
||||||
**Note:** Modules are services - each module has its own `cmd/{service}/main.go` entry point.
|
|
||||||
|
|
||||||
### 2. Module Manifest (`pkg/module/manifest.go`)
|
### 2. Module Manifest (`pkg/module/manifest.go`)
|
||||||
- `Manifest` struct with:
|
- `Manifest` struct with:
|
||||||
- Name, Version, Dependencies (service names)
|
- Name, Version, Dependencies
|
||||||
- Permissions list
|
- Permissions list
|
||||||
- gRPC service definitions
|
- Routes definition
|
||||||
- Database schema information
|
|
||||||
- `module.yaml` schema definition
|
- `module.yaml` schema definition
|
||||||
- Manifest parsing and validation
|
- Manifest parsing and validation
|
||||||
|
|
||||||
### 3. Service Registration Integration
|
### 3. Module Registry (`internal/registry/registry.go`)
|
||||||
- Integration with Consul service registry (from Epic 1)
|
- Thread-safe module map
|
||||||
- Service registration helpers
|
|
||||||
- Service discovery integration
|
|
||||||
- Health check integration
|
|
||||||
|
|
||||||
### 4. Module Registry (`internal/registry/module_registry.go`)
|
|
||||||
- Thread-safe module map (for tracking feature services)
|
|
||||||
- `Register(m IModule)` function
|
- `Register(m IModule)` function
|
||||||
- `All() []IModule` function
|
- `All() []IModule` function
|
||||||
- `Get(name string) (IModule, error)` function
|
- `Get(name string) (IModule, error)` function
|
||||||
- Dependency validation (check service dependencies are available)
|
- Dependency validation (check dependencies are satisfied)
|
||||||
- Duplicate name detection
|
- Duplicate name detection
|
||||||
- Version compatibility checking
|
- Version compatibility checking
|
||||||
- Dependency cycle detection
|
- Dependency cycle detection
|
||||||
|
|
||||||
**Note:** This is separate from the service registry (Consul) - this tracks feature services for dependency resolution.
|
|
||||||
|
|
||||||
## Implementation Steps
|
## Implementation Steps
|
||||||
|
|
||||||
1. **Create Module Interface**
|
1. **Create Module Interface**
|
||||||
@@ -79,24 +68,18 @@ This story creates the foundation for feature services (modules) by defining the
|
|||||||
- Test duplicate detection
|
- Test duplicate detection
|
||||||
|
|
||||||
## Acceptance Criteria
|
## Acceptance Criteria
|
||||||
- [x] Feature services can register via module interface
|
- [ ] Modules can register via `registry.Register()`
|
||||||
- [x] Registry validates service dependencies
|
- [ ] Registry validates dependencies
|
||||||
- [x] Registry prevents duplicate registrations
|
- [ ] Registry prevents duplicate registrations
|
||||||
- [x] Module interface supports service architecture
|
- [ ] Module interface is extensible
|
||||||
- [x] Dependency cycles are detected
|
- [ ] Dependency cycles are detected
|
||||||
- [x] Version compatibility is checked
|
- [ ] Version compatibility is checked
|
||||||
- [x] Service registration with Consul is integrated
|
|
||||||
- [x] Feature services can discover core services via service registry
|
|
||||||
|
|
||||||
## Related ADRs
|
## Related ADRs
|
||||||
- [ADR-0021: Module Loading Strategy](../../adr/0021-module-loading-strategy.md)
|
- [ADR-0021: Module Loading Strategy](../../adr/0021-module-loading-strategy.md)
|
||||||
- [ADR-0029: Microservices Architecture](../../adr/0029-microservices-architecture.md)
|
|
||||||
- [ADR-0030: Service Communication Strategy](../../adr/0030-service-communication-strategy.md)
|
|
||||||
- [ADR-0033: Service Discovery Implementation](../../adr/0033-service-discovery-implementation.md)
|
|
||||||
|
|
||||||
## Files to Create/Modify
|
## Files to Create/Modify
|
||||||
- `pkg/module/module.go` - Module interface for feature services
|
- `pkg/module/module.go` - Module interface
|
||||||
- `pkg/module/manifest.go` - Module manifest
|
- `pkg/module/manifest.go` - Module manifest
|
||||||
- `internal/registry/module_registry.go` - Module registry (for dependency tracking)
|
- `internal/registry/registry.go` - Module registry
|
||||||
- Integration with `internal/registry/consul/` (service registry from Epic 1)
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,97 +1,83 @@
|
|||||||
# Story 3.3: Service Loader and Initialization
|
# Story 3.3: Module Loader and Initialization
|
||||||
|
|
||||||
## Metadata
|
## Metadata
|
||||||
- **Story ID**: 3.3
|
- **Story ID**: 3.3
|
||||||
- **Title**: Service Loader and Initialization
|
- **Title**: Module Loader and Initialization
|
||||||
- **Epic**: 3 - Module Framework (Feature Services)
|
- **Epic**: 3 - Module Framework
|
||||||
- **Status**: Pending
|
- **Status**: Pending
|
||||||
- **Priority**: High
|
- **Priority**: High
|
||||||
- **Estimated Time**: 6-8 hours
|
- **Estimated Time**: 6-8 hours
|
||||||
- **Dependencies**: 3.1, 1.2, 1.7
|
- **Dependencies**: 3.1, 1.2
|
||||||
|
|
||||||
## Goal
|
## Goal
|
||||||
Implement service initialization helpers for feature services with dependency resolution, Consul registration, and automatic migration execution. Each service initializes itself in its own entry point.
|
Implement module loading (static and dynamic) with dependency resolution and automatic initialization.
|
||||||
|
|
||||||
## Description
|
## Description
|
||||||
This story implements service initialization helpers that feature services use in their entry points (`cmd/{service}/main.go`). Services initialize themselves, resolve dependencies (other services), register with Consul, run migrations, and start their gRPC servers. The loader provides helpers for common service initialization patterns.
|
This story implements the complete module loading system that discovers modules, resolves dependencies, initializes them in the correct order, and runs their migrations. It supports both static registration (preferred) and dynamic plugin loading.
|
||||||
|
|
||||||
## Deliverables
|
## Deliverables
|
||||||
|
|
||||||
### 1. Service Initialization Helpers (`internal/service/init.go`)
|
### 1. Module Loader (`internal/pluginloader/loader.go`)
|
||||||
- `InitializeService(cfg, module IModule)` function
|
- Support static registration (preferred method)
|
||||||
- Bootstrap core kernel services
|
- Optional: Go plugin loading (`.so` files)
|
||||||
- Initialize service-specific components
|
- Module discovery from `modules/*/module.yaml`
|
||||||
- Register with Consul service registry
|
- Loader interface for extensibility
|
||||||
- Run database migrations (per-service schema)
|
|
||||||
- Start gRPC server
|
|
||||||
- Handle graceful shutdown
|
|
||||||
|
|
||||||
### 2. Service Dependency Resolution (`internal/service/deps.go`)
|
### 2. Static Loader (`internal/pluginloader/static_loader.go`)
|
||||||
- Resolve service dependencies (check other services are available via Consul)
|
- Import modules via side-effect imports
|
||||||
- Wait for dependent services to be healthy
|
- Collect all registered modules
|
||||||
- Service discovery integration
|
- Module discovery and registration
|
||||||
- Dependency validation
|
|
||||||
|
|
||||||
### 3. Service Bootstrap Pattern (`internal/service/bootstrap.go`)
|
### 3. Optional Plugin Loader (`internal/pluginloader/plugin_loader.go`)
|
||||||
- Common bootstrap pattern for feature services
|
- Scan `./plugins/*.so` files
|
||||||
- FX lifecycle integration
|
- Load via `plugin.Open()`
|
||||||
- Service registration helpers
|
- Extract and validate module symbols
|
||||||
- Health check setup
|
- Version compatibility checking
|
||||||
- Migration runner (per-service)
|
|
||||||
|
|
||||||
### 4. Service Template/Scaffolding
|
### 4. Module Initializer (`internal/module/initializer.go`)
|
||||||
- Service template generator
|
- Collect all registered modules
|
||||||
- Standard service structure
|
- Resolve dependency order (topological sort)
|
||||||
- Example service entry point
|
- Initialize each module's `Init()` fx.Option
|
||||||
|
- Merge all options into main fx container
|
||||||
|
- Run migrations in dependency order
|
||||||
|
- Handle errors gracefully
|
||||||
|
|
||||||
**Note:** Each feature service has its own `cmd/{service}/main.go` that uses these helpers to initialize itself.
|
### 5. FX Lifecycle Integration
|
||||||
|
- Call `OnStart()` during app startup
|
||||||
|
- Call `OnStop()` during graceful shutdown
|
||||||
|
- Proper error handling
|
||||||
|
|
||||||
## Implementation Steps
|
## Implementation Steps
|
||||||
|
|
||||||
1. **Create Service Initialization Helpers**
|
1. **Create Module Loader**
|
||||||
- Create `internal/service/init.go`
|
- Create `internal/pluginloader/loader.go`
|
||||||
- Implement service bootstrap pattern
|
- Define loader interface
|
||||||
- Integrate with Consul service registry
|
|
||||||
|
|
||||||
2. **Implement Dependency Resolution**
|
2. **Implement Static Loader**
|
||||||
- Create `internal/service/deps.go`
|
- Create `internal/pluginloader/static_loader.go`
|
||||||
- Check service dependencies via Consul
|
- Implement static module loading
|
||||||
- Wait for services to be healthy
|
|
||||||
|
|
||||||
3. **Create Service Bootstrap**
|
3. **Implement Module Initializer**
|
||||||
- Create `internal/service/bootstrap.go`
|
- Create `internal/module/initializer.go`
|
||||||
- Common bootstrap pattern
|
- Implement dependency resolution
|
||||||
- FX lifecycle integration
|
- Implement initialization
|
||||||
|
|
||||||
4. **Create Service Template**
|
4. **Integrate with FX**
|
||||||
- Service template generator
|
- Add lifecycle hooks
|
||||||
- Example service entry point
|
- Test initialization
|
||||||
|
|
||||||
5. **Test Service Initialization**
|
|
||||||
- Test service startup
|
|
||||||
- Test Consul registration
|
|
||||||
- Test dependency resolution
|
|
||||||
|
|
||||||
## Acceptance Criteria
|
## Acceptance Criteria
|
||||||
- [x] Service initialization helpers work correctly
|
- [ ] Modules load in correct dependency order
|
||||||
- [x] Services register with Consul automatically
|
- [ ] Module migrations run automatically
|
||||||
- [x] Service migrations run on startup (per-service schema)
|
- [ ] Module initialization integrates with FX
|
||||||
- [x] Service dependency resolution works via Consul
|
- [ ] Lifecycle hooks work correctly
|
||||||
- [x] Services wait for dependencies to be healthy
|
- [ ] Dependency resolution handles cycles
|
||||||
- [x] FX lifecycle integration works
|
- [ ] Errors are handled gracefully
|
||||||
- [x] Service bootstrap pattern is reusable
|
|
||||||
- [x] Service template generator creates standard structure
|
|
||||||
|
|
||||||
## Related ADRs
|
|
||||||
- [ADR-0021: Module Loading Strategy](../../adr/0021-module-loading-strategy.md)
|
|
||||||
- [ADR-0029: Microservices Architecture](../../adr/0029-microservices-architecture.md)
|
|
||||||
- [ADR-0030: Service Communication Strategy](../../adr/0030-service-communication-strategy.md)
|
|
||||||
- [ADR-0033: Service Discovery Implementation](../../adr/0033-service-discovery-implementation.md)
|
|
||||||
|
|
||||||
## Files to Create/Modify
|
## Files to Create/Modify
|
||||||
- `internal/service/init.go` - Service initialization helpers
|
- `internal/pluginloader/loader.go` - Loader interface
|
||||||
- `internal/service/deps.go` - Service dependency resolution
|
- `internal/pluginloader/static_loader.go` - Static loader
|
||||||
- `internal/service/bootstrap.go` - Service bootstrap pattern
|
- `internal/pluginloader/plugin_loader.go` - Plugin loader (optional)
|
||||||
- `internal/service/template.go` - Service template generator
|
- `internal/module/initializer.go` - Module initializer
|
||||||
- Example: `cmd/blog-service/main.go` - Example service entry point
|
- `internal/di/container.go` - Integrate module initialization
|
||||||
|
|
||||||
|
|||||||
@@ -1,29 +1,28 @@
|
|||||||
# Story 3.4: Service Management CLI Tool
|
# Story 3.4: Module Management CLI Tool
|
||||||
|
|
||||||
## Metadata
|
## Metadata
|
||||||
- **Story ID**: 3.4
|
- **Story ID**: 3.4
|
||||||
- **Title**: Service Management CLI Tool
|
- **Title**: Module Management CLI Tool
|
||||||
- **Epic**: 3 - Module Framework (Feature Services)
|
- **Epic**: 3 - Module Framework
|
||||||
- **Status**: Pending
|
- **Status**: Pending
|
||||||
- **Priority**: Medium
|
- **Priority**: Medium
|
||||||
- **Estimated Time**: 4-5 hours
|
- **Estimated Time**: 4-5 hours
|
||||||
- **Dependencies**: 3.1, 3.3
|
- **Dependencies**: 3.1, 3.3
|
||||||
|
|
||||||
## Goal
|
## Goal
|
||||||
Provide CLI tooling for managing feature services, validating dependencies, and testing service registration with Consul.
|
Provide CLI tooling for managing modules, validating dependencies, and testing module loading.
|
||||||
|
|
||||||
## Description
|
## Description
|
||||||
This story creates a CLI tool that allows developers and operators to manage feature services, validate service dependencies, test service registration, and inspect service information via Consul.
|
This story creates a CLI tool that allows developers and operators to manage modules, validate dependencies, test module loading, and inspect module information.
|
||||||
|
|
||||||
## Deliverables
|
## Deliverables
|
||||||
|
|
||||||
### 1. CLI Tool (`cmd/platformctl/main.go`)
|
### 1. CLI Tool (`cmd/platformctl/main.go`)
|
||||||
- `platformctl services list` - List all services registered in Consul
|
- `platformctl modules list` - List all loaded modules with versions
|
||||||
- `platformctl services validate` - Validate service dependencies
|
- `platformctl modules validate` - Validate module dependencies
|
||||||
- `platformctl services test <service>` - Test service registration and health
|
- `platformctl modules test <module>` - Test module loading
|
||||||
- `platformctl services info <service>` - Show service details from Consul
|
- `platformctl modules info <module>` - Show module details
|
||||||
- `platformctl services dependencies <service>` - Show service dependencies
|
- `platformctl modules dependencies <module>` - Show module dependencies
|
||||||
- `platformctl services health <service>` - Check service health
|
|
||||||
- Command-line argument parsing
|
- Command-line argument parsing
|
||||||
- Error handling and user-friendly output
|
- Error handling and user-friendly output
|
||||||
|
|
||||||
@@ -50,13 +49,12 @@ This story creates a CLI tool that allows developers and operators to manage fea
|
|||||||
- Add install commands
|
- Add install commands
|
||||||
|
|
||||||
## Acceptance Criteria
|
## Acceptance Criteria
|
||||||
- [x] CLI tool lists all services from Consul
|
- [ ] CLI tool lists all modules
|
||||||
- [x] Service dependency validation works
|
- [ ] Dependency validation works
|
||||||
- [x] Service health checking works
|
- [ ] Module testing works
|
||||||
- [x] CLI is installable and usable
|
- [ ] CLI is installable and usable
|
||||||
- [x] Commands provide helpful output
|
- [ ] Commands provide helpful output
|
||||||
- [x] Error messages are clear
|
- [ ] Error messages are clear
|
||||||
- [x] Integration with Consul service registry works
|
|
||||||
|
|
||||||
## Files to Create/Modify
|
## Files to Create/Modify
|
||||||
- `cmd/platformctl/main.go` - CLI tool
|
- `cmd/platformctl/main.go` - CLI tool
|
||||||
|
|||||||
@@ -1,38 +1,138 @@
|
|||||||
# Story 3.5: Service Registry and Discovery (Verification)
|
# Story 3.5: Service Registry and Discovery
|
||||||
|
|
||||||
## Metadata
|
## Metadata
|
||||||
- **Story ID**: 3.5
|
- **Story ID**: 3.5
|
||||||
- **Title**: Service Registry and Discovery (Verification)
|
- **Title**: Service Registry and Discovery
|
||||||
- **Epic**: 3 - Module Framework (Feature Services)
|
- **Epic**: 3 - Module Framework
|
||||||
- **Status**: Pending
|
- **Status**: Pending
|
||||||
- **Priority**: High
|
- **Priority**: High
|
||||||
- **Estimated Time**: 2-3 hours
|
- **Estimated Time**: 5-6 hours
|
||||||
- **Dependencies**: 1.7, 3.1
|
- **Dependencies**: 1.7, 3.1
|
||||||
|
|
||||||
## Goal
|
## Goal
|
||||||
Verify and document integration of Consul service registry (implemented in Epic 1) with feature services. Ensure all services can register and discover each other correctly.
|
Implement a service registry that enables service discovery for microservices, allowing services to locate and communicate with each other.
|
||||||
|
|
||||||
## Description
|
## Description
|
||||||
This story verifies that the Consul service registry implemented in Epic 1 (Story 1.7) works correctly with feature services. It ensures service registration, discovery, and health checking work for both core services and feature services.
|
This story creates a service registry system supporting Consul and Kubernetes service discovery. The registry enables service discovery, health checking, and automatic service registration.
|
||||||
|
|
||||||
## Deliverables
|
## Deliverables
|
||||||
|
|
||||||
### 1. Service Registry Verification
|
### 1. Service Registry Interface (`pkg/services/registry.go`)
|
||||||
- Verify Consul registry implementation from Epic 1 works correctly
|
- `ServiceRegistry` interface with:
|
||||||
- Test service registration for feature services
|
- `Register(service ServiceInfo) error` - Register a service
|
||||||
- Test service discovery for feature services
|
- `Deregister(serviceID string) error` - Deregister a service
|
||||||
- Verify health checking integration
|
- `Discover(serviceName string) ([]ServiceInfo, error)` - Discover services
|
||||||
|
- `GetService(serviceName string) (ServiceInfo, error)` - Get specific service
|
||||||
|
- `ListServices() ([]ServiceInfo, error)` - List all services
|
||||||
|
- `HealthCheck(serviceID string) error` - Check service health
|
||||||
|
|
||||||
### 2. Integration Documentation
|
### 2. Service Info Structure
|
||||||
- Document how feature services register with Consul
|
- `ServiceInfo` struct with:
|
||||||
- Document service discovery patterns for feature services
|
- ID, Name, Version
|
||||||
- Update examples to show feature service registration
|
- Address (host:port)
|
||||||
|
- Protocol (local, grpc, http)
|
||||||
|
- Health status
|
||||||
|
- Metadata
|
||||||
|
|
||||||
### 3. Integration Tests
|
### 3. Consul Registry (`internal/services/registry/consul.go`)
|
||||||
- Test feature service registration
|
- Consul integration (primary for production)
|
||||||
- Test feature service discovery
|
- Service registration and discovery
|
||||||
- Test health checking for feature services
|
- Health checking
|
||||||
- Test service client integration with Consul
|
- Automatic service registration
|
||||||
|
|
||||||
**Note:** Service registry implementation is in Epic 1 (Story 1.7). This story verifies integration with feature services.
|
### 4. Kubernetes Service Discovery (`internal/services/registry/kubernetes.go`)
|
||||||
|
- Kubernetes service discovery
|
||||||
|
- Service health checking
|
||||||
|
- Automatic service registration via K8s services
|
||||||
|
|
||||||
|
### 5. Service Registration
|
||||||
|
- Auto-register services on startup
|
||||||
|
- Health check endpoints
|
||||||
|
- Graceful deregistration on shutdown
|
||||||
|
|
||||||
|
### 6. Configuration
|
||||||
|
- Registry configuration in `config/default.yaml`:
|
||||||
|
```yaml
|
||||||
|
service_registry:
|
||||||
|
type: consul # consul, kubernetes, etcd
|
||||||
|
consul:
|
||||||
|
address: localhost:8500
|
||||||
|
kubernetes:
|
||||||
|
namespace: default
|
||||||
|
etcd:
|
||||||
|
endpoints:
|
||||||
|
- localhost:2379
|
||||||
|
```
|
||||||
|
|
||||||
|
### 7. Integration
|
||||||
|
- Integrate with service factory
|
||||||
|
- Auto-register core services
|
||||||
|
- Support module service registration
|
||||||
|
|
||||||
|
## Implementation Steps
|
||||||
|
|
||||||
|
1. **Create Service Registry Interface**
|
||||||
|
- Create `pkg/services/registry.go`
|
||||||
|
- Define ServiceRegistry interface
|
||||||
|
- Define ServiceInfo struct
|
||||||
|
|
||||||
|
2. **Implement Consul Registry**
|
||||||
|
- Create `internal/services/registry/consul.go`
|
||||||
|
- Implement Consul integration
|
||||||
|
- Add health checking
|
||||||
|
|
||||||
|
3. **Implement Kubernetes Registry**
|
||||||
|
- Create `internal/services/registry/kubernetes.go`
|
||||||
|
- Implement K8s service discovery
|
||||||
|
- Add health checking
|
||||||
|
|
||||||
|
4. **Add Service Registration**
|
||||||
|
- Auto-register services on startup
|
||||||
|
- Add health check endpoints
|
||||||
|
- Handle graceful shutdown
|
||||||
|
|
||||||
|
5. **Add Configuration**
|
||||||
|
- Add registry configuration
|
||||||
|
- Support multiple registry types
|
||||||
|
|
||||||
|
6. **Integrate with Service Factory**
|
||||||
|
- Use registry for service discovery
|
||||||
|
- Resolve services via registry
|
||||||
|
|
||||||
|
## Acceptance Criteria
|
||||||
|
- [ ] Service registry interface is defined
|
||||||
|
- [ ] Consul registry works correctly
|
||||||
|
- [ ] Kubernetes registry works correctly
|
||||||
|
- [ ] Services are auto-registered on startup
|
||||||
|
- [ ] Service discovery works
|
||||||
|
- [ ] Health checking works
|
||||||
|
- [ ] Registry is configurable
|
||||||
|
- [ ] Graceful deregistration works
|
||||||
|
|
||||||
|
## Related ADRs
|
||||||
|
- [ADR-0029: Microservices Architecture](../../adr/0029-microservices-architecture.md)
|
||||||
|
- [ADR-0030: Service Communication Strategy](../../adr/0030-service-communication-strategy.md)
|
||||||
|
|
||||||
|
## Implementation Notes
|
||||||
|
- Consul is the primary registry for production
|
||||||
|
- Kubernetes service discovery for K8s deployments
|
||||||
|
- Health checks should be lightweight
|
||||||
|
- Support service versioning
|
||||||
|
|
||||||
|
## Testing
|
||||||
|
```bash
|
||||||
|
# Test service registry
|
||||||
|
go test ./internal/services/registry/...
|
||||||
|
|
||||||
|
# Test service discovery
|
||||||
|
go test ./internal/services/registry/... -run TestDiscovery
|
||||||
|
```
|
||||||
|
|
||||||
|
## Files to Create/Modify
|
||||||
|
- `pkg/services/registry.go` - Service registry interface
|
||||||
|
- `internal/services/registry/consul.go` - Consul registry
|
||||||
|
- `internal/services/registry/kubernetes.go` - Kubernetes registry
|
||||||
|
- `internal/services/factory.go` - Integrate with registry
|
||||||
|
- `internal/di/providers.go` - Add registry provider
|
||||||
|
- `config/default.yaml` - Add registry configuration
|
||||||
|
|
||||||
|
|||||||
@@ -1,51 +1,48 @@
|
|||||||
# Epic 3: Module Framework (Feature Services)
|
# Epic 3: Module Framework
|
||||||
|
|
||||||
## Overview
|
## Overview
|
||||||
Design and implement the module framework for feature services. Modules are implemented as independent services with their own entry points (`cmd/{service}/`), gRPC servers, and database schemas. The framework provides module interfaces, service registration, permission code generation, and CLI tooling for service management.
|
Design and implement complete module system interface, build module registry with dependency resolution, create permission code generation from module manifests, implement module loader supporting static and dynamic loading, add module lifecycle management and initialization, and provide CLI tooling for module management.
|
||||||
|
|
||||||
**Key Principle:** Modules are services - each module is an independently deployable service that registers with Consul and communicates via service clients.
|
|
||||||
|
|
||||||
## Stories
|
## Stories
|
||||||
|
|
||||||
### 3.1 Module System Interface and Service Registry
|
### 3.1 Module System Interface and Registry
|
||||||
- [Story: 3.1 - Module System Interface](./3.1-module-system-interface.md)
|
- [Story: 3.1 - Module System Interface](./3.1-module-system-interface.md)
|
||||||
- **Goal:** Design module interface for feature services with service registration and dependency resolution.
|
- **Goal:** Design and implement the complete module system interface with registration, dependency resolution, and lifecycle management.
|
||||||
- **Deliverables:** Module interface, module manifest, service registration integration
|
- **Deliverables:** Module interface, module manifest, module registry
|
||||||
|
|
||||||
### 3.2 Permission Code Generation System
|
### 3.2 Permission Code Generation System
|
||||||
- [Story: 3.2 - Permission Code Generation](./3.2-permission-code-generation.md)
|
- [Story: 3.2 - Permission Code Generation](./3.2-permission-code-generation.md)
|
||||||
- **Goal:** Create automated permission code generation from module manifests to ensure type-safe permission constants.
|
- **Goal:** Create automated permission code generation from module manifests to ensure type-safe permission constants.
|
||||||
- **Deliverables:** Permission generation script, Go generate integration, Makefile integration
|
- **Deliverables:** Permission generation script, Go generate integration, Makefile integration
|
||||||
|
|
||||||
### 3.3 Service Loader and Initialization
|
### 3.3 Module Loader and Initialization
|
||||||
- [Story: 3.3 - Service Loader](./3.3-module-loader.md)
|
- [Story: 3.3 - Module Loader](./3.3-module-loader.md)
|
||||||
- **Goal:** Implement service loading and initialization for feature services with dependency resolution.
|
- **Goal:** Implement module loading (static and dynamic) with dependency resolution and automatic initialization.
|
||||||
- **Deliverables:** Service loader, service initialization, FX lifecycle integration, Consul registration
|
- **Deliverables:** Module loader, static loader, plugin loader, module initializer, FX lifecycle integration
|
||||||
|
|
||||||
### 3.4 Service Management CLI Tool
|
### 3.4 Module Management CLI Tool
|
||||||
- [Story: 3.4 - Service CLI](./3.4-module-cli.md)
|
- [Story: 3.4 - Module CLI](./3.4-module-cli.md)
|
||||||
- **Goal:** Provide CLI tooling for managing feature services, validating dependencies, and testing service loading.
|
- **Goal:** Provide CLI tooling for managing modules, validating dependencies, and testing module loading.
|
||||||
- **Deliverables:** CLI tool, Makefile integration
|
- **Deliverables:** CLI tool, Makefile integration
|
||||||
|
|
||||||
### 3.5 Service Registry and Discovery
|
### 3.5 Service Registry and Discovery
|
||||||
- [Story: 3.5 - Service Registry](./3.5-service-registry.md)
|
- [Story: 3.5 - Service Registry](./3.5-service-registry.md)
|
||||||
- **Goal:** Implement Consul-based service registry for service discovery (already implemented in Epic 1, verify integration).
|
- **Goal:** Implement a service registry that enables service discovery for microservices.
|
||||||
- **Deliverables:** Service registry interface verification, Consul integration verification
|
- **Deliverables:** Service registry interface, Consul registry, Kubernetes registry, service registration
|
||||||
|
|
||||||
## Deliverables Checklist
|
## Deliverables Checklist
|
||||||
- [ ] Module interface for feature services
|
- [ ] Module interface and registration system
|
||||||
- [ ] Service registration with Consul
|
- [ ] Static module registry working
|
||||||
- [ ] Permission code generation tool
|
- [ ] Permission code generation tool
|
||||||
- [ ] Service loader with dependency resolution
|
- [ ] Module loader with dependency resolution
|
||||||
- [ ] Service initialization in service entry points
|
- [ ] Module initialization in main app
|
||||||
- [ ] CLI tool for service management
|
- [ ] CLI tool for module management
|
||||||
- [ ] Service registry integration verified
|
- [ ] Service registry for discovery
|
||||||
|
|
||||||
## Acceptance Criteria
|
## Acceptance Criteria
|
||||||
- Feature services can register via module interface
|
- Modules can register via `registry.Register()`
|
||||||
- Permission constants are generated from `module.yaml`
|
- Permission constants are generated from `module.yaml`
|
||||||
- Services load in correct dependency order
|
- Modules load in correct dependency order
|
||||||
- Service migrations run on startup
|
- Module migrations run on startup
|
||||||
- Services register with Consul automatically
|
- `platformctl modules list` shows all modules
|
||||||
- `platformctl services list` shows all services
|
- Integration test: load multiple modules and verify initialization
|
||||||
- Integration test: load multiple services and verify Consul registration
|
|
||||||
|
|||||||
@@ -1,194 +1,169 @@
|
|||||||
# Story 4.1: Complete Blog Service
|
# Story 4.1: Complete Blog Module
|
||||||
|
|
||||||
## Metadata
|
## Metadata
|
||||||
- **Story ID**: 4.1
|
- **Story ID**: 4.1
|
||||||
- **Title**: Complete Blog Service
|
- **Title**: Complete Blog Module
|
||||||
- **Epic**: 4 - Sample Feature Service (Blog Service)
|
- **Epic**: 4 - Sample Feature Module (Blog)
|
||||||
- **Status**: Pending
|
- **Status**: Pending
|
||||||
- **Priority**: High
|
- **Priority**: High
|
||||||
- **Estimated Time**: 12-15 hours
|
- **Estimated Time**: 10-12 hours
|
||||||
- **Dependencies**: 3.1, 3.2, 3.3, 2.3
|
- **Dependencies**: 3.1, 3.2, 3.3, 2.3
|
||||||
|
|
||||||
## Goal
|
## Goal
|
||||||
Create a complete sample blog service to demonstrate the framework. The Blog Service is an independent service with its own entry point, gRPC server, and database schema. It uses service clients to communicate with core services.
|
Create a complete sample blog module to demonstrate the framework, showing how to add routes, permissions, database entities, and services. This serves as a reference implementation for future developers.
|
||||||
|
|
||||||
## Description
|
## Description
|
||||||
This story implements a complete blog service with blog posts, CRUD operations via gRPC, proper authorization via Authz Service, and integration with core services. The service demonstrates all aspects of feature service development including service entry point, gRPC server, domain models, repositories, services, and Consul registration.
|
This story implements a complete blog module with blog posts, CRUD operations, proper authorization, and integration with the core platform. The module demonstrates all aspects of module development including domain models, repositories, services, API handlers, and module registration.
|
||||||
|
|
||||||
## Deliverables
|
## Deliverables
|
||||||
|
|
||||||
### 1. Blog Service Entry Point (`cmd/blog-service/main.go`)
|
### 1. Blog Module Structure
|
||||||
- Independent service entry point
|
- Create `modules/blog/` directory with proper structure:
|
||||||
- Bootstrap with core kernel services
|
|
||||||
- Register with Consul service registry
|
|
||||||
- Start gRPC server on configured port (default: 8091)
|
|
||||||
- Graceful shutdown with service deregistration
|
|
||||||
|
|
||||||
### 2. Blog Service Structure
|
|
||||||
- Create `services/blog/` directory with proper structure:
|
|
||||||
```
|
```
|
||||||
cmd/blog-service/
|
modules/blog/
|
||||||
└── main.go # Service entry point
|
|
||||||
|
|
||||||
services/blog/
|
|
||||||
├── go.mod
|
├── go.mod
|
||||||
├── module.yaml
|
├── module.yaml
|
||||||
├── api/
|
|
||||||
│ └── proto/
|
|
||||||
│ └── blog.proto # gRPC service definition
|
|
||||||
├── internal/
|
├── internal/
|
||||||
│ ├── api/
|
│ ├── api/
|
||||||
│ │ └── server.go # gRPC server implementation
|
│ │ └── handler.go
|
||||||
│ ├── domain/
|
│ ├── domain/
|
||||||
│ │ ├── post.go
|
│ │ ├── post.go
|
||||||
│ │ └── post_repo.go
|
│ │ └── post_repo.go
|
||||||
│ ├── service/
|
│ ├── service/
|
||||||
│ │ └── post_service.go
|
│ │ └── post_service.go
|
||||||
│ └── database/
|
│ └── ent/
|
||||||
│ └── client.go # Database connection (blog schema)
|
│ └── schema/
|
||||||
└── ent/
|
│ └── post.go
|
||||||
└── schema/
|
└── pkg/
|
||||||
└── post.go
|
└── module.go
|
||||||
```
|
```
|
||||||
|
- Initialize `go.mod` for blog module
|
||||||
|
|
||||||
### 3. gRPC Service Definition (`api/proto/blog.proto`)
|
### 2. Module Manifest (`modules/blog/module.yaml`)
|
||||||
- `CreatePostRequest` / `CreatePostResponse`
|
- Define module metadata (name, version, dependencies)
|
||||||
- `GetPostRequest` / `GetPostResponse`
|
|
||||||
- `ListPostsRequest` / `ListPostsResponse`
|
|
||||||
- `UpdatePostRequest` / `UpdatePostResponse`
|
|
||||||
- `DeletePostRequest` / `DeletePostResponse`
|
|
||||||
- `BlogService` gRPC service definition
|
|
||||||
|
|
||||||
### 4. gRPC Server Implementation (`services/blog/internal/api/server.go`)
|
|
||||||
- gRPC server implementation
|
|
||||||
- Handlers for all blog operations
|
|
||||||
- Integration with Blog Service business logic
|
|
||||||
|
|
||||||
### 5. Service Manifest (`services/blog/module.yaml`)
|
|
||||||
- Define service metadata (name, version, dependencies)
|
|
||||||
- Define permissions (blog.post.create, read, update, delete)
|
- Define permissions (blog.post.create, read, update, delete)
|
||||||
- Define gRPC service information
|
- Define routes with permission requirements
|
||||||
|
|
||||||
### 6. Blog Domain Model
|
### 3. Blog Domain Model
|
||||||
- `Post` domain entity in `services/blog/internal/domain/post.go`
|
- `Post` domain entity in `modules/blog/internal/domain/post.go`
|
||||||
- Ent schema in `services/blog/ent/schema/post.go`:
|
- Ent schema in `modules/blog/internal/ent/schema/post.go`:
|
||||||
- Fields: title, content, author_id (references Identity Service users)
|
- Fields: title, content, author_id (FK to user)
|
||||||
- Indexes: author_id, created_at
|
- Indexes: author_id, created_at
|
||||||
- Timestamps: created_at, updated_at
|
- Timestamps: created_at, updated_at
|
||||||
- Generate Ent code for blog service
|
- Generate Ent code for blog module
|
||||||
- Database connection with blog schema
|
|
||||||
|
|
||||||
### 7. Blog Repository
|
### 4. Blog Repository
|
||||||
- `PostRepository` interface in `services/blog/internal/domain/post_repo.go`
|
- `PostRepository` interface in `modules/blog/internal/domain/post_repo.go`
|
||||||
- Implementation using Ent client (blog schema)
|
- Implementation using Ent client (shared from core)
|
||||||
- CRUD operations: Create, FindByID, FindByAuthor, Update, Delete
|
- CRUD operations: Create, FindByID, FindByAuthor, Update, Delete
|
||||||
- Pagination support
|
- Pagination support
|
||||||
|
|
||||||
### 8. Blog Service
|
### 5. Blog Service
|
||||||
- `PostService` in `services/blog/internal/service/post_service.go`
|
- `PostService` in `modules/blog/internal/service/post_service.go`
|
||||||
- Business logic for creating/updating posts
|
- Business logic for creating/updating posts
|
||||||
- Validation (title length, content requirements)
|
- Validation (title length, content requirements)
|
||||||
- Authorization checks via AuthzServiceClient
|
- Authorization checks (author can only update own posts)
|
||||||
- Uses service clients for inter-service communication:
|
- Uses service clients for inter-service communication:
|
||||||
- `IdentityServiceClient` - to get user information
|
- `IdentityServiceClient` - to get user information
|
||||||
- `AuthzServiceClient` - for authorization checks
|
- `AuthzServiceClient` - for authorization checks
|
||||||
- `AuditServiceClient` - for audit logging
|
- `AuditServiceClient` - for audit logging
|
||||||
|
|
||||||
### 9. Service Registration
|
### 6. Blog API Handlers
|
||||||
- Register with Consul on startup
|
- API handlers in `modules/blog/internal/api/handler.go`:
|
||||||
- Health check endpoint for Consul
|
- `POST /api/v1/blog/posts` - Create post
|
||||||
- Service metadata (name: `blog-service`, port: 8091)
|
- `GET /api/v1/blog/posts/:id` - Get post
|
||||||
- Deregister on shutdown
|
- `GET /api/v1/blog/posts` - List posts (with pagination)
|
||||||
|
- `PUT /api/v1/blog/posts/:id` - Update post
|
||||||
|
- `DELETE /api/v1/blog/posts/:id` - Delete post
|
||||||
|
- Use authorization middleware for all endpoints
|
||||||
|
- Register handlers in module's `Init()`
|
||||||
|
|
||||||
|
### 7. Blog Module Implementation
|
||||||
|
- Module implementation in `modules/blog/pkg/module.go`:
|
||||||
|
- Implement IModule interface
|
||||||
|
- Define Init() fx.Option
|
||||||
|
- Define Migrations()
|
||||||
|
- Register module in init()
|
||||||
|
|
||||||
|
### 8. Integration
|
||||||
|
- Update main `go.mod` to include blog module
|
||||||
|
- Import blog module in `cmd/platform/main.go`
|
||||||
|
- Run permission generation: `make generate`
|
||||||
|
- Verify blog permissions are generated
|
||||||
|
|
||||||
|
### 9. Tests
|
||||||
|
- Integration test in `modules/blog/internal/api/handler_test.go`:
|
||||||
|
- Test creating post with valid permission
|
||||||
|
- Test creating post without permission (403)
|
||||||
|
- Test updating own post vs other's post
|
||||||
|
- Test pagination
|
||||||
|
- Unit tests for service and repository
|
||||||
|
|
||||||
## Implementation Steps
|
## Implementation Steps
|
||||||
|
|
||||||
1. **Create Service Entry Point**
|
1. **Create Module Structure**
|
||||||
- Create `cmd/blog-service/main.go`
|
- Create directory structure
|
||||||
- Bootstrap with core kernel services
|
|
||||||
- Register with Consul
|
|
||||||
|
|
||||||
2. **Create Service Structure**
|
|
||||||
- Create `services/blog/` directory
|
|
||||||
- Initialize go.mod
|
- Initialize go.mod
|
||||||
|
|
||||||
|
2. **Create Module Manifest**
|
||||||
- Create module.yaml
|
- Create module.yaml
|
||||||
|
- Define permissions and routes
|
||||||
|
|
||||||
3. **Define gRPC Service**
|
3. **Create Domain Model**
|
||||||
- Create `api/proto/blog.proto`
|
|
||||||
- Define all RPCs
|
|
||||||
- Generate Go code
|
|
||||||
|
|
||||||
4. **Create Domain Model**
|
|
||||||
- Create Post entity
|
- Create Post entity
|
||||||
- Create Ent schema (blog schema)
|
- Create Ent schema
|
||||||
- Generate Ent code
|
- Generate Ent code
|
||||||
|
|
||||||
5. **Create Repository**
|
4. **Create Repository**
|
||||||
- Create repository interface
|
- Create repository interface
|
||||||
- Implement using Ent (blog schema)
|
- Implement using Ent
|
||||||
|
|
||||||
6. **Create Service**
|
5. **Create Service**
|
||||||
- Create service with business logic
|
- Create service with business logic
|
||||||
- Integrate with service clients (Identity, Authz, Audit)
|
|
||||||
- Add validation and authorization
|
- Add validation and authorization
|
||||||
|
|
||||||
7. **Implement gRPC Server**
|
6. **Create API Handlers**
|
||||||
- Create gRPC server implementation
|
- Create handlers
|
||||||
- Wire up handlers
|
- Add authorization middleware
|
||||||
- Start server
|
- Register routes
|
||||||
|
|
||||||
8. **Service Registration**
|
7. **Create Module Implementation**
|
||||||
- Register with Consul on startup
|
- Implement IModule interface
|
||||||
- Set up health checks
|
- Register module
|
||||||
- Test service discovery
|
|
||||||
|
8. **Integrate with Platform**
|
||||||
|
- Import module in main
|
||||||
|
- Generate permissions
|
||||||
|
- Test integration
|
||||||
|
|
||||||
9. **Add Tests**
|
9. **Add Tests**
|
||||||
- Create integration tests
|
- Create integration tests
|
||||||
- Create unit tests
|
- Create unit tests
|
||||||
- Test service client integration
|
|
||||||
|
|
||||||
## Acceptance Criteria
|
## Acceptance Criteria
|
||||||
- [x] Blog service is independently deployable
|
- [ ] Blog module loads on platform startup
|
||||||
- [x] Service entry point exists at `cmd/blog-service/main.go`
|
- [ ] `POST /api/v1/blog/posts` requires `blog.post.create` permission
|
||||||
- [x] Service registers with Consul on startup
|
- [ ] User can create, read, update, delete posts
|
||||||
- [x] gRPC server starts on configured port (8091)
|
- [ ] Authorization enforced (users can only edit own posts)
|
||||||
- [x] CreatePost RPC requires `blog.post.create` permission (via AuthzServiceClient)
|
- [ ] Integration test: full CRUD flow works
|
||||||
- [x] User can create, read, update, delete posts via gRPC
|
- [ ] Audit logs record all blog actions
|
||||||
- [x] Service uses IdentityServiceClient for user operations
|
- [ ] Permissions are generated correctly
|
||||||
- [x] Service uses AuthzServiceClient for authorization
|
- [ ] Module migrations run on startup
|
||||||
- [x] Service uses AuditServiceClient for audit logging
|
|
||||||
- [x] Service has its own database schema (blog schema)
|
|
||||||
- [x] Service can be discovered by API Gateway via Consul
|
|
||||||
- [x] Integration test: full CRUD flow works via gRPC
|
|
||||||
- [x] Service migrations run on startup
|
|
||||||
|
|
||||||
## Related ADRs
|
## Related ADRs
|
||||||
- [ADR-0029: Microservices Architecture](../../adr/0029-microservices-architecture.md)
|
- [ADR-0029: Microservices Architecture](../../adr/0029-microservices-architecture.md)
|
||||||
- [ADR-0030: Service Communication Strategy](../../adr/0030-service-communication-strategy.md)
|
- [ADR-0030: Service Communication Strategy](../../adr/0030-service-communication-strategy.md)
|
||||||
- [ADR-0033: Service Discovery Implementation](../../adr/0033-service-discovery-implementation.md)
|
- See module framework ADRs
|
||||||
|
|
||||||
## Testing
|
|
||||||
```bash
|
|
||||||
# Test Blog Service
|
|
||||||
go test ./services/blog/...
|
|
||||||
|
|
||||||
# Test service startup
|
|
||||||
go run cmd/blog-service/main.go
|
|
||||||
|
|
||||||
# Test gRPC service
|
|
||||||
grpcurl -plaintext localhost:8091 list
|
|
||||||
grpcurl -plaintext -d '{"title":"Test","content":"Content"}' \
|
|
||||||
localhost:8091 blog.BlogService/CreatePost
|
|
||||||
```
|
|
||||||
|
|
||||||
## Files to Create/Modify
|
## Files to Create/Modify
|
||||||
- `cmd/blog-service/main.go` - Service entry point
|
- `modules/blog/module.yaml` - Module manifest
|
||||||
- `services/blog/module.yaml` - Service manifest
|
- `modules/blog/go.mod` - Module dependencies
|
||||||
- `services/blog/go.mod` - Service dependencies
|
- `modules/blog/internal/domain/post.go` - Domain model
|
||||||
- `api/proto/blog.proto` - gRPC service definition
|
- `modules/blog/internal/ent/schema/post.go` - Ent schema
|
||||||
- `services/blog/internal/api/server.go` - gRPC server implementation
|
- `modules/blog/internal/domain/post_repo.go` - Repository
|
||||||
- `services/blog/internal/domain/post.go` - Domain model
|
- `modules/blog/internal/service/post_service.go` - Service
|
||||||
- `services/blog/ent/schema/post.go` - Ent schema (blog schema)
|
- `modules/blog/internal/api/handler.go` - API handlers
|
||||||
- `services/blog/internal/domain/post_repo.go` - Repository
|
- `modules/blog/pkg/module.go` - Module implementation
|
||||||
- `services/blog/internal/service/post_service.go` - Service logic
|
- `go.mod` - Add blog module
|
||||||
- `config/default.yaml` - Add blog service configuration
|
- `cmd/platform/main.go` - Import blog module
|
||||||
|
|
||||||
|
|||||||
@@ -1,39 +1,31 @@
|
|||||||
# Epic 4: Sample Feature Service (Blog Service)
|
# Epic 4: Sample Feature Module (Blog)
|
||||||
|
|
||||||
## Overview
|
## Overview
|
||||||
Create a complete sample feature service (Blog Service) to demonstrate the framework. The Blog Service is an independent service with its own entry point (`cmd/blog-service/`), gRPC server, and database schema. It uses service clients to communicate with core services (Auth, Identity, Authz, Audit). This serves as a reference implementation for future developers creating feature services.
|
Create a complete sample module (Blog) to demonstrate the framework, showing how to add routes, permissions, database entities, and services. The Blog module is an independent service that uses service clients to communicate with core services. Provide reference implementation for future developers.
|
||||||
|
|
||||||
**Key Principle:** Blog Service demonstrates how to create a feature service that integrates with the platform using service clients and Consul service discovery.
|
|
||||||
|
|
||||||
## Stories
|
## Stories
|
||||||
|
|
||||||
### 4.1 Complete Blog Service
|
### 4.1 Complete Blog Module
|
||||||
- [Story: 4.1 - Blog Service](./4.1-blog-module.md)
|
- [Story: 4.1 - Blog Module](./4.1-blog-module.md)
|
||||||
- **Goal:** Create a complete sample blog service to demonstrate the framework.
|
- **Goal:** Create a complete sample blog module to demonstrate the framework.
|
||||||
- **Deliverables:** Complete blog service with entry point, gRPC server, database schema, CRUD operations, permissions, service client integration, and integration tests
|
- **Deliverables:** Complete blog module with CRUD operations, permissions, database entities, services, API handlers, and integration tests
|
||||||
|
|
||||||
## Deliverables Checklist
|
## Deliverables Checklist
|
||||||
- [ ] Blog service entry point (`cmd/blog-service/main.go`)
|
- [ ] Blog module directory structure created
|
||||||
- [ ] Blog service directory structure (`services/blog/`)
|
- [ ] Module manifest defines permissions and routes
|
||||||
- [ ] gRPC service definition (`api/proto/blog.proto`)
|
|
||||||
- [ ] gRPC server implementation
|
|
||||||
- [ ] Service manifest defines permissions
|
|
||||||
- [ ] Blog post domain model defined
|
- [ ] Blog post domain model defined
|
||||||
- [ ] Ent schema for blog posts (blog schema)
|
- [ ] Ent schema for blog posts created
|
||||||
- [ ] Repository implements CRUD operations
|
- [ ] Repository implements CRUD operations
|
||||||
- [ ] Service layer implements business logic
|
- [ ] Service layer implements business logic
|
||||||
- [ ] Service uses service clients (Identity, Authz, Audit)
|
- [ ] API endpoints for blog posts working
|
||||||
- [ ] Service registers with Consul
|
- [ ] Module integrated with core platform
|
||||||
- [ ] Integration tests passing
|
- [ ] Integration tests passing
|
||||||
|
|
||||||
## Acceptance Criteria
|
## Acceptance Criteria
|
||||||
- Blog service is independently deployable
|
- Blog module can be registered with core platform
|
||||||
- Service entry point exists at `cmd/blog-service/main.go`
|
- Permissions are generated for blog module
|
||||||
- Service registers with Consul on startup
|
|
||||||
- gRPC server starts on configured port
|
|
||||||
- CRUD operations work for blog posts
|
- CRUD operations work for blog posts
|
||||||
- Service uses IdentityServiceClient for user operations
|
- API endpoints require proper authentication
|
||||||
- Service uses AuthzServiceClient for authorization
|
- Module migrations run on startup
|
||||||
- Service uses AuditServiceClient for audit logging
|
- Blog posts are associated with users
|
||||||
- Service has its own database schema (blog schema)
|
- Authorization enforced (users can only edit own posts)
|
||||||
- Service can be discovered by API Gateway via Consul
|
|
||||||
|
|||||||
@@ -1,116 +1,150 @@
|
|||||||
# Story 5.7: Advanced gRPC Features
|
# Story 5.7: gRPC Service Definitions and Clients
|
||||||
|
|
||||||
## Metadata
|
## Metadata
|
||||||
- **Story ID**: 5.7
|
- **Story ID**: 5.7
|
||||||
- **Title**: Advanced gRPC Features
|
- **Title**: gRPC Service Definitions and Clients
|
||||||
- **Epic**: 5 - Infrastructure Adapters
|
- **Epic**: 5 - Infrastructure Adapters
|
||||||
- **Status**: Pending
|
- **Status**: Pending
|
||||||
- **Priority**: Medium
|
- **Priority**: Medium
|
||||||
- **Estimated Time**: 6-8 hours
|
- **Estimated Time**: 8-10 hours
|
||||||
- **Dependencies**: 1.7, 2.1, 2.2, 2.3, 2.5
|
- **Dependencies**: 1.7, 3.5
|
||||||
|
|
||||||
## Goal
|
## Goal
|
||||||
Enhance gRPC implementation with advanced features including streaming RPCs, gRPC-Gateway for HTTP access, advanced error handling, and gRPC middleware. Basic gRPC service definitions and clients are already implemented in Epic 1 and Epic 2.
|
Implement gRPC service definitions and clients to enable microservices communication, allowing modules to be extracted as independent services.
|
||||||
|
|
||||||
## Description
|
## Description
|
||||||
This story enhances the gRPC implementation that was established in Epic 1 (Service Client Interfaces) and Epic 2 (each service implements its own gRPC server). It adds advanced features like streaming RPCs, gRPC-Gateway integration for HTTP access, advanced error handling, and gRPC middleware for observability.
|
This story implements gRPC service definitions for core services and gRPC clients that implement the service client interfaces. This enables modules to communicate with services over the network when deployed as microservices.
|
||||||
|
|
||||||
## Deliverables
|
## Deliverables
|
||||||
|
|
||||||
### 1. Streaming RPC Support
|
### 1. gRPC Service Definitions (`api/proto/`)
|
||||||
- Add streaming RPC definitions to existing proto files
|
- Define Protocol Buffer files for core services:
|
||||||
- Server-side streaming implementations
|
- `identity.proto` - Identity service
|
||||||
- Client-side streaming implementations
|
- `auth.proto` - Authentication service
|
||||||
- Bidirectional streaming support
|
- `authz.proto` - Authorization service
|
||||||
- Example: Stream audit logs, stream user events
|
- `permission.proto` - Permission service
|
||||||
|
- `audit.proto` - Audit service
|
||||||
|
- Use protobuf v3
|
||||||
|
- Include proper message definitions
|
||||||
|
- Include service definitions
|
||||||
|
|
||||||
### 2. gRPC-Gateway Integration
|
### 2. gRPC Server Implementations (`internal/services/grpc/server/`)
|
||||||
- HTTP to gRPC gateway for REST API access
|
- Implement gRPC servers for each service:
|
||||||
- Generate REST endpoints from gRPC services
|
- `identity_server.go` - Identity gRPC server
|
||||||
- Support for both gRPC and HTTP on same service
|
- `auth_server.go` - Auth gRPC server
|
||||||
- Gateway configuration
|
- `authz_server.go` - Authz gRPC server
|
||||||
|
- Server implementations wrap existing services
|
||||||
|
- Error handling and validation
|
||||||
|
- Request/response conversion
|
||||||
|
|
||||||
### 3. Advanced Error Handling
|
### 3. gRPC Client Implementations (`internal/services/grpc/client/`)
|
||||||
- Structured error responses
|
- Implement gRPC clients that satisfy service client interfaces:
|
||||||
- gRPC status codes mapping
|
- `grpc_identity_client.go` - Identity gRPC client
|
||||||
- Error details and metadata
|
- `grpc_auth_client.go` - Auth gRPC client
|
||||||
- Error propagation across services
|
- `grpc_authz_client.go` - Authz gRPC client
|
||||||
|
- Connection pooling
|
||||||
|
- Retry logic
|
||||||
|
- Circuit breaker support
|
||||||
|
- Timeout handling
|
||||||
|
|
||||||
### 4. gRPC Middleware
|
### 4. gRPC Server Setup
|
||||||
- Logging middleware
|
- gRPC server initialization
|
||||||
- Metrics middleware
|
- Service registration
|
||||||
- Tracing middleware (OpenTelemetry)
|
- Health check service
|
||||||
- Authentication middleware
|
- Reflection service (development)
|
||||||
- Rate limiting middleware
|
- Integration with HTTP server (gRPC-Gateway optional)
|
||||||
|
|
||||||
### 5. gRPC Health Check Service
|
### 5. Code Generation
|
||||||
- Standard gRPC health check protocol
|
- `Makefile` target for protobuf generation
|
||||||
- Per-service health status
|
- Generate Go code from `.proto` files
|
||||||
- Integration with Consul health checks
|
- Generate gRPC server and client stubs
|
||||||
|
|
||||||
**Note:** Basic gRPC service definitions (`api/proto/*.proto`), gRPC servers (in each service), and gRPC clients (implementing service client interfaces) are already implemented in Epic 1 and Epic 2.
|
### 6. Configuration
|
||||||
|
- gRPC configuration in `config/default.yaml`:
|
||||||
|
```yaml
|
||||||
|
grpc:
|
||||||
|
enabled: false # Enable gRPC server
|
||||||
|
port: 9090
|
||||||
|
reflection: true # Enable reflection (dev)
|
||||||
|
```
|
||||||
|
|
||||||
|
### 7. Integration
|
||||||
|
- Integrate with service factory
|
||||||
|
- Support switching between local and gRPC clients
|
||||||
|
- Service registry integration for gRPC services
|
||||||
|
|
||||||
## Implementation Steps
|
## Implementation Steps
|
||||||
|
|
||||||
1. **Add Streaming RPCs**
|
1. **Install Dependencies**
|
||||||
- Update existing proto files with streaming RPCs
|
```bash
|
||||||
- Implement streaming handlers in services
|
go get google.golang.org/grpc
|
||||||
- Update clients to support streaming
|
go get google.golang.org/protobuf
|
||||||
|
go install google.golang.org/protobuf/cmd/protoc-gen-go
|
||||||
|
go install google.golang.org/grpc/cmd/protoc-gen-go-grpc
|
||||||
|
```
|
||||||
|
|
||||||
2. **Implement gRPC-Gateway**
|
2. **Define Protocol Buffers**
|
||||||
- Install gRPC-Gateway
|
- Create `api/proto/` directory
|
||||||
- Generate gateway code from proto files
|
- Define `.proto` files for each service
|
||||||
- Configure gateway endpoints
|
- Define messages and services
|
||||||
|
|
||||||
3. **Enhance Error Handling**
|
3. **Generate gRPC Code**
|
||||||
- Implement structured error responses
|
- Create `Makefile` target
|
||||||
- Add error details and metadata
|
- Generate Go code from protobuf
|
||||||
- Update error propagation
|
|
||||||
|
|
||||||
4. **Add gRPC Middleware**
|
4. **Implement gRPC Servers**
|
||||||
- Create middleware chain
|
- Create server implementations
|
||||||
- Add logging, metrics, tracing middleware
|
- Wrap existing services
|
||||||
- Integrate with existing observability
|
- Handle errors and validation
|
||||||
|
|
||||||
5. **Implement Health Check Service**
|
5. **Implement gRPC Clients**
|
||||||
- Add gRPC health check service
|
- Create client implementations
|
||||||
- Integrate with Consul health checks
|
- Implement service client interfaces
|
||||||
|
- Add connection management
|
||||||
|
|
||||||
|
6. **Integrate with Service Factory**
|
||||||
|
- Update factory to support gRPC clients
|
||||||
|
- Add gRPC server startup
|
||||||
|
|
||||||
## Acceptance Criteria
|
## Acceptance Criteria
|
||||||
- [x] Streaming RPCs work correctly
|
- [ ] gRPC service definitions are created
|
||||||
- [x] gRPC-Gateway provides HTTP access to gRPC services
|
- [ ] gRPC servers are implemented
|
||||||
- [x] Advanced error handling works across services
|
- [ ] gRPC clients implement service interfaces
|
||||||
- [x] gRPC middleware provides observability
|
- [ ] Service factory can create gRPC clients
|
||||||
- [x] Health check service integrates with Consul
|
- [ ] gRPC services can be enabled via configuration
|
||||||
- [x] All services support advanced gRPC features
|
- [ ] Code generation works
|
||||||
|
- [ ] gRPC clients work with service registry
|
||||||
|
|
||||||
## Related ADRs
|
## Related ADRs
|
||||||
- [ADR-0029: Microservices Architecture](../../adr/0029-microservices-architecture.md)
|
- [ADR-0029: Microservices Architecture](../../adr/0029-microservices-architecture.md)
|
||||||
- [ADR-0030: Service Communication Strategy](../../adr/0030-service-communication-strategy.md)
|
- [ADR-0030: Service Communication Strategy](../../adr/0030-service-communication-strategy.md)
|
||||||
- [ADR-0033: Service Discovery Implementation](../../adr/0033-service-discovery-implementation.md)
|
|
||||||
|
|
||||||
## Implementation Notes
|
## Implementation Notes
|
||||||
- Basic gRPC is already implemented in Epic 1 and Epic 2
|
- Use protobuf v3
|
||||||
- Focus on advanced features: streaming, gateway, middleware
|
- Support both unary and streaming RPCs
|
||||||
- Maintain backward compatibility with existing gRPC services
|
- Implement proper error handling
|
||||||
- Add OpenTelemetry instrumentation to middleware
|
- Add OpenTelemetry instrumentation
|
||||||
|
- Support service versioning
|
||||||
|
|
||||||
## Testing
|
## Testing
|
||||||
```bash
|
```bash
|
||||||
# Test streaming RPCs
|
# Generate protobuf code
|
||||||
go test ./services/*/internal/api/... -run Streaming
|
make generate-proto
|
||||||
|
|
||||||
# Test gRPC-Gateway
|
# Test gRPC servers
|
||||||
curl http://localhost:8080/api/v1/users/123
|
go test ./internal/services/grpc/server/...
|
||||||
|
|
||||||
# Test health check service
|
# Test gRPC clients
|
||||||
grpcurl -plaintext localhost:8081 grpc.health.v1.Health/Check
|
go test ./internal/services/grpc/client/...
|
||||||
```
|
```
|
||||||
|
|
||||||
## Files to Create/Modify
|
## Files to Create/Modify
|
||||||
- `api/proto/*.proto` - Add streaming RPCs to existing proto files
|
- `api/proto/identity.proto` - Identity service definition
|
||||||
- `internal/server/grpc/middleware.go` - gRPC middleware
|
- `api/proto/auth.proto` - Auth service definition
|
||||||
- `internal/server/grpc/gateway.go` - gRPC-Gateway integration
|
- `api/proto/authz.proto` - Authz service definition
|
||||||
- `internal/server/grpc/health.go` - Health check service
|
- `internal/services/grpc/server/` - gRPC server implementations
|
||||||
- `Makefile` - Add gateway generation target
|
- `internal/services/grpc/client/` - gRPC client implementations
|
||||||
|
- `internal/services/factory.go` - Add gRPC client support
|
||||||
|
- `Makefile` - Add protobuf generation
|
||||||
|
- `config/default.yaml` - Add gRPC configuration
|
||||||
|
|
||||||
|
|||||||
@@ -1,9 +1,7 @@
|
|||||||
# Epic 5: Infrastructure Adapters
|
# Epic 5: Infrastructure Adapters
|
||||||
|
|
||||||
## Overview
|
## Overview
|
||||||
Implement infrastructure adapters (cache, queue, blob storage, email) that services use. These adapters are shared infrastructure components that all services can use. Make adapters swappable via interfaces, add scheduler/background jobs system, and implement event bus (in-process and Kafka).
|
Implement infrastructure adapters (cache, queue, blob storage, email), make adapters swappable via interfaces, add scheduler/background jobs system, and implement event bus (in-process and Kafka).
|
||||||
|
|
||||||
**Note:** gRPC service definitions and clients are already implemented in Epic 1 (Story 1.7) and Epic 2 (each service implements its own gRPC server). Story 5.7 focuses on advanced gRPC features and enhancements.
|
|
||||||
|
|
||||||
## Stories
|
## Stories
|
||||||
|
|
||||||
@@ -37,12 +35,10 @@ Implement infrastructure adapters (cache, queue, blob storage, email) that servi
|
|||||||
- **Goal:** Implement secret store integration supporting Vault and AWS Secrets Manager.
|
- **Goal:** Implement secret store integration supporting Vault and AWS Secrets Manager.
|
||||||
- **Deliverables:** Secret store interface, Vault implementation, AWS implementation, config integration
|
- **Deliverables:** Secret store interface, Vault implementation, AWS implementation, config integration
|
||||||
|
|
||||||
### 5.7 Advanced gRPC Features
|
### 5.7 gRPC Service Definitions and Clients
|
||||||
- [Story: 5.7 - Advanced gRPC Features](./5.7-grpc-services.md)
|
- [Story: 5.7 - gRPC Services](./5.7-grpc-services.md)
|
||||||
- **Goal:** Enhance gRPC implementation with advanced features (streaming, advanced error handling, gRPC-Gateway).
|
- **Goal:** Implement gRPC service definitions and clients to enable microservices communication.
|
||||||
- **Deliverables:** Streaming RPCs, gRPC-Gateway integration, advanced error handling, gRPC middleware
|
- **Deliverables:** gRPC service definitions, gRPC servers, gRPC clients, code generation
|
||||||
|
|
||||||
**Note:** Basic gRPC service definitions and clients are already implemented in Epic 1 (Story 1.7) and Epic 2 (each service implements its own gRPC server).
|
|
||||||
|
|
||||||
## Deliverables Checklist
|
## Deliverables Checklist
|
||||||
- [ ] Cache adapter (Redis) working
|
- [ ] Cache adapter (Redis) working
|
||||||
@@ -51,7 +47,7 @@ Implement infrastructure adapters (cache, queue, blob storage, email) that servi
|
|||||||
- [ ] Email notification system
|
- [ ] Email notification system
|
||||||
- [ ] Scheduler and background jobs
|
- [ ] Scheduler and background jobs
|
||||||
- [ ] Secret store integration (optional)
|
- [ ] Secret store integration (optional)
|
||||||
- [ ] Advanced gRPC features (streaming, gRPC-Gateway)
|
- [ ] gRPC service definitions and clients
|
||||||
|
|
||||||
## Acceptance Criteria
|
## Acceptance Criteria
|
||||||
- Cache stores and retrieves data correctly
|
- Cache stores and retrieves data correctly
|
||||||
@@ -59,6 +55,4 @@ Implement infrastructure adapters (cache, queue, blob storage, email) that servi
|
|||||||
- Files can be uploaded and downloaded
|
- Files can be uploaded and downloaded
|
||||||
- Email notifications are sent
|
- Email notifications are sent
|
||||||
- Background jobs run on schedule
|
- Background jobs run on schedule
|
||||||
- gRPC streaming works
|
|
||||||
- gRPC-Gateway provides HTTP access to gRPC services
|
|
||||||
- Integration test: full infrastructure stack works
|
- Integration test: full infrastructure stack works
|
||||||
|
|||||||
@@ -1,16 +1,14 @@
|
|||||||
# Epic 6: Observability & Production Readiness
|
# Epic 6: Observability & Production Readiness
|
||||||
|
|
||||||
## Overview
|
## Overview
|
||||||
Enhance observability with full OpenTelemetry integration across all services, add comprehensive error reporting (Sentry), create Grafana dashboards for service monitoring, improve logging with request correlation across services, add rate limiting (primarily at API Gateway), security hardening, and optimize performance for microservices architecture.
|
Enhance observability with full OpenTelemetry integration, add comprehensive error reporting (Sentry), create Grafana dashboards, improve logging with request correlation, add rate limiting and security hardening, and optimize performance.
|
||||||
|
|
||||||
**Note:** Observability spans all services - distributed tracing, service-level metrics, and cross-service log correlation.
|
|
||||||
|
|
||||||
## Stories
|
## Stories
|
||||||
|
|
||||||
### 6.1 Enhanced Observability
|
### 6.1 Enhanced Observability
|
||||||
- [Story: 6.1 - Enhanced Observability](./6.1-enhanced-observability.md)
|
- [Story: 6.1 - Enhanced Observability](./6.1-enhanced-observability.md)
|
||||||
- **Goal:** Enhance observability with full OpenTelemetry integration across all services, comprehensive Prometheus metrics per service, and improved logging with trace correlation.
|
- **Goal:** Enhance observability with full OpenTelemetry integration, comprehensive Prometheus metrics, and improved logging.
|
||||||
- **Deliverables:** Complete OpenTelemetry integration, expanded metrics per service, enhanced logging with trace IDs
|
- **Deliverables:** Complete OpenTelemetry integration, expanded metrics, enhanced logging
|
||||||
|
|
||||||
### 6.2 Error Reporting (Sentry)
|
### 6.2 Error Reporting (Sentry)
|
||||||
- [Story: 6.2 - Error Reporting](./6.2-error-reporting.md)
|
- [Story: 6.2 - Error Reporting](./6.2-error-reporting.md)
|
||||||
@@ -19,15 +17,13 @@ Enhance observability with full OpenTelemetry integration across all services, a
|
|||||||
|
|
||||||
### 6.3 Grafana Dashboards
|
### 6.3 Grafana Dashboards
|
||||||
- [Story: 6.3 - Grafana Dashboards](./6.3-grafana-dashboards.md)
|
- [Story: 6.3 - Grafana Dashboards](./6.3-grafana-dashboards.md)
|
||||||
- **Goal:** Create comprehensive Grafana dashboards for monitoring all services.
|
- **Goal:** Create comprehensive Grafana dashboards for monitoring.
|
||||||
- **Deliverables:** Grafana dashboard JSON files per service, service-level dashboards, cross-service dashboards, documentation
|
- **Deliverables:** Grafana dashboard JSON files, documentation
|
||||||
|
|
||||||
### 6.4 Rate Limiting
|
### 6.4 Rate Limiting
|
||||||
- [Story: 6.4 - Rate Limiting](./6.4-rate-limiting.md)
|
- [Story: 6.4 - Rate Limiting](./6.4-rate-limiting.md)
|
||||||
- **Goal:** Implement rate limiting primarily at API Gateway level, with per-service rate limiting support.
|
- **Goal:** Implement rate limiting to prevent API abuse.
|
||||||
- **Deliverables:** Rate limiting middleware for API Gateway, per-service rate limiting support, Redis-backed rate limiting
|
- **Deliverables:** Rate limiting middleware, configuration
|
||||||
|
|
||||||
**Note:** Rate limiting is primarily implemented in API Gateway (Epic 1, Story 1.8). This story adds per-service rate limiting capabilities.
|
|
||||||
|
|
||||||
### 6.5 Security Hardening
|
### 6.5 Security Hardening
|
||||||
- [Story: 6.5 - Security Hardening](./6.5-security-hardening.md)
|
- [Story: 6.5 - Security Hardening](./6.5-security-hardening.md)
|
||||||
@@ -50,11 +46,10 @@ Enhance observability with full OpenTelemetry integration across all services, a
|
|||||||
- [ ] Performance optimizations
|
- [ ] Performance optimizations
|
||||||
|
|
||||||
## Acceptance Criteria
|
## Acceptance Criteria
|
||||||
- Distributed traces span all services and are visible in Jaeger
|
- Traces are exported and visible in Jaeger
|
||||||
- Errors are reported to Sentry with service context
|
- Errors are reported to Sentry with context
|
||||||
- Logs include request IDs and trace IDs for correlation across services
|
- Logs include request IDs and trace IDs
|
||||||
- Metrics are exposed per service and scraped by Prometheus
|
- Metrics are exposed and scraped by Prometheus
|
||||||
- Rate limiting prevents abuse (primarily at API Gateway)
|
- Rate limiting prevents abuse
|
||||||
- Security headers are present on all services
|
- Security headers are present
|
||||||
- Performance meets SLA (< 100ms p95 for auth endpoints)
|
- Performance meets SLA (< 100ms p95 for auth endpoints)
|
||||||
- Service-level dashboards available in Grafana
|
|
||||||
|
|||||||
@@ -1,31 +1,29 @@
|
|||||||
# Epic 7: Testing, Documentation & CI/CD
|
# Epic 7: Testing, Documentation & CI/CD
|
||||||
|
|
||||||
## Overview
|
## Overview
|
||||||
Comprehensive test coverage (unit, integration, contract) for all services, complete documentation, production-ready CI/CD pipeline for multiple services, Docker images and deployment guides for each service, and developer tooling.
|
Comprehensive test coverage (unit, integration, contract), complete documentation, production-ready CI/CD pipeline, Docker images and deployment guides, and developer tooling.
|
||||||
|
|
||||||
**Note:** Testing strategy covers unit tests per service, integration tests across services, and contract tests for service APIs.
|
|
||||||
|
|
||||||
## Stories
|
## Stories
|
||||||
|
|
||||||
### 7.1 Comprehensive Testing Suite
|
### 7.1 Comprehensive Testing Suite
|
||||||
- [Story: 7.1 - Testing Suite](./7.1-testing-suite.md)
|
- [Story: 7.1 - Testing Suite](./7.1-testing-suite.md)
|
||||||
- **Goal:** Achieve comprehensive test coverage with unit tests per service, integration tests across services, and contract tests for service APIs.
|
- **Goal:** Achieve comprehensive test coverage with unit tests, integration tests, and contract tests.
|
||||||
- **Deliverables:** Unit tests per service (>80% coverage), integration tests across services, contract tests for gRPC APIs, load tests
|
- **Deliverables:** Unit tests (>80% coverage), integration tests, contract tests, load tests
|
||||||
|
|
||||||
### 7.2 Complete Documentation
|
### 7.2 Complete Documentation
|
||||||
- [Story: 7.2 - Documentation](./7.2-documentation.md)
|
- [Story: 7.2 - Documentation](./7.2-documentation.md)
|
||||||
- **Goal:** Create comprehensive documentation covering architecture, API, operations, and developer guides for microservices architecture.
|
- **Goal:** Create comprehensive documentation covering architecture, API, operations, and developer guides.
|
||||||
- **Deliverables:** README, architecture docs, API docs, operations guides, code examples
|
- **Deliverables:** README, architecture docs, API docs, operations guides, code examples
|
||||||
|
|
||||||
### 7.3 CI/CD Pipeline Enhancement
|
### 7.3 CI/CD Pipeline Enhancement
|
||||||
- [Story: 7.3 - CI/CD Enhancement](./7.3-cicd-enhancement.md)
|
- [Story: 7.3 - CI/CD Enhancement](./7.3-cicd-enhancement.md)
|
||||||
- **Goal:** Enhance CI/CD pipeline with comprehensive testing for all services, security scanning, and release automation.
|
- **Goal:** Enhance CI/CD pipeline with comprehensive testing, security scanning, and release automation.
|
||||||
- **Deliverables:** Enhanced CI pipeline, release workflow, security scanning, multi-service builds
|
- **Deliverables:** Enhanced CI pipeline, release workflow, security scanning
|
||||||
|
|
||||||
### 7.4 Docker and Deployment
|
### 7.4 Docker and Deployment
|
||||||
- [Story: 7.4 - Docker & Deployment](./7.4-docker-deployment.md)
|
- [Story: 7.4 - Docker & Deployment](./7.4-docker-deployment.md)
|
||||||
- **Goal:** Create production-ready Docker images for each service and comprehensive deployment guides for microservices architecture.
|
- **Goal:** Create production-ready Docker images and comprehensive deployment guides.
|
||||||
- **Deliverables:** Docker images per service, Docker Compose for all services, deployment guides, developer tooling
|
- **Deliverables:** Docker images, Docker Compose, deployment guides, developer tooling
|
||||||
|
|
||||||
## Deliverables Checklist
|
## Deliverables Checklist
|
||||||
- [ ] >80% test coverage
|
- [ ] >80% test coverage
|
||||||
@@ -36,12 +34,9 @@ Comprehensive test coverage (unit, integration, contract) for all services, comp
|
|||||||
- [ ] Developer tooling and scripts
|
- [ ] Developer tooling and scripts
|
||||||
|
|
||||||
## Acceptance Criteria
|
## Acceptance Criteria
|
||||||
- All tests pass in CI for all services
|
- All tests pass in CI
|
||||||
- Code coverage >80% per service
|
- Code coverage >80%
|
||||||
- Integration tests verify service-to-service communication
|
|
||||||
- Contract tests verify service APIs
|
|
||||||
- Documentation is complete and accurate
|
- Documentation is complete and accurate
|
||||||
- Docker images build and run successfully for all services
|
- Docker images build and run successfully
|
||||||
- Docker Compose orchestrates all services correctly
|
|
||||||
- Deployment guides are tested
|
- Deployment guides are tested
|
||||||
- New developers can set up environment in <30 minutes
|
- New developers can set up environment in <30 minutes
|
||||||
|
|||||||
@@ -1,62 +1,42 @@
|
|||||||
# Story 8.3: Additional Sample Feature Services
|
# Story 8.3: Additional Sample Modules
|
||||||
|
|
||||||
## Metadata
|
## Metadata
|
||||||
- **Story ID**: 8.3
|
- **Story ID**: 8.3
|
||||||
- **Title**: Additional Sample Feature Services
|
- **Title**: Additional Sample Modules
|
||||||
- **Epic**: 8 - Advanced Features & Polish
|
- **Epic**: 8 - Advanced Features & Polish
|
||||||
- **Status**: Pending
|
- **Status**: Pending
|
||||||
- **Priority**: Low
|
- **Priority**: Low
|
||||||
- **Estimated Time**: 12-15 hours
|
- **Estimated Time**: 10-12 hours
|
||||||
- **Dependencies**: 4.1
|
- **Dependencies**: 4.1
|
||||||
|
|
||||||
## Goal
|
## Goal
|
||||||
Create additional sample feature services to demonstrate different use cases and patterns. Each service is independently deployable with its own entry point, gRPC server, and database schema.
|
Create additional sample modules to demonstrate different use cases and patterns.
|
||||||
|
|
||||||
## Description
|
## Description
|
||||||
This story creates additional sample feature services (Notification Service, Analytics Service) to show different service patterns and use cases. Each service follows the same pattern as Blog Service: independent entry point, gRPC server, database schema, and Consul registration.
|
This story creates additional sample modules (notification, analytics) to show different module patterns and use cases.
|
||||||
|
|
||||||
## Deliverables
|
## Deliverables
|
||||||
|
|
||||||
### 1. Notification Service
|
### 1. Notification Module
|
||||||
- Service entry point: `cmd/notification-service/main.go`
|
- Create `modules/notification/`:
|
||||||
- Service structure: `services/notification/`
|
|
||||||
- gRPC service definition: `api/proto/notification.proto`
|
|
||||||
- Features:
|
|
||||||
- Email templates
|
- Email templates
|
||||||
- Notification preferences
|
- Notification preferences
|
||||||
- Notification history
|
- Notification history
|
||||||
- gRPC API for sending notifications
|
- Notification API
|
||||||
- Database schema: `notification` schema
|
|
||||||
- Service registration with Consul
|
|
||||||
|
|
||||||
### 2. Analytics Service
|
### 2. Analytics Module
|
||||||
- Service entry point: `cmd/analytics-service/main.go`
|
- Create `modules/analytics/`:
|
||||||
- Service structure: `services/analytics/`
|
|
||||||
- gRPC service definition: `api/proto/analytics.proto`
|
|
||||||
- Features:
|
|
||||||
- Event tracking
|
- Event tracking
|
||||||
- Analytics dashboard API
|
- Analytics dashboard API
|
||||||
- Export functionality
|
- Export functionality
|
||||||
- Database schema: `analytics` schema
|
|
||||||
- Service registration with Consul
|
|
||||||
- Uses Event Bus for event ingestion
|
|
||||||
|
|
||||||
## Acceptance Criteria
|
## Acceptance Criteria
|
||||||
- [x] Notification Service is independently deployable
|
- [ ] Notification module works
|
||||||
- [x] Analytics Service is independently deployable
|
- [ ] Analytics module works
|
||||||
- [x] Each service has its own entry point and gRPC server
|
- [ ] Modules demonstrate different patterns
|
||||||
- [x] Services register with Consul
|
- [ ] Modules are well-documented
|
||||||
- [x] Services demonstrate different patterns (event-driven, etc.)
|
|
||||||
- [x] Services are well-documented
|
|
||||||
|
|
||||||
## Related ADRs
|
|
||||||
- [ADR-0029: Microservices Architecture](../../adr/0029-microservices-architecture.md)
|
|
||||||
- [ADR-0030: Service Communication Strategy](../../adr/0030-service-communication-strategy.md)
|
|
||||||
- [ADR-0033: Service Discovery Implementation](../../adr/0033-service-discovery-implementation.md)
|
|
||||||
|
|
||||||
## Files to Create/Modify
|
## Files to Create/Modify
|
||||||
- `cmd/notification-service/main.go` - Notification Service entry point
|
- `modules/notification/` - Notification module
|
||||||
- `services/notification/` - Notification Service implementation
|
- `modules/analytics/` - Analytics module
|
||||||
- `cmd/analytics-service/main.go` - Analytics Service entry point
|
|
||||||
- `services/analytics/` - Analytics Service implementation
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,9 +1,7 @@
|
|||||||
# Epic 8: Advanced Features & Polish
|
# Epic 8: Advanced Features & Polish
|
||||||
|
|
||||||
## Overview
|
## Overview
|
||||||
Add advanced features (OIDC, GraphQL), performance optimization, additional sample feature services, and final polish and bug fixes.
|
Add advanced features (OIDC, GraphQL, API Gateway), performance optimization, additional sample modules, and final polish and bug fixes.
|
||||||
|
|
||||||
**Note:** API Gateway is now in Epic 1 (Story 1.8) as core infrastructure, not an advanced feature.
|
|
||||||
|
|
||||||
## Stories
|
## Stories
|
||||||
|
|
||||||
@@ -17,10 +15,10 @@ Add advanced features (OIDC, GraphQL), performance optimization, additional samp
|
|||||||
- **Goal:** Add optional GraphQL API alongside REST API.
|
- **Goal:** Add optional GraphQL API alongside REST API.
|
||||||
- **Deliverables:** GraphQL schema, resolvers, GraphQL endpoint
|
- **Deliverables:** GraphQL schema, resolvers, GraphQL endpoint
|
||||||
|
|
||||||
### 8.3 Additional Sample Feature Services
|
### 8.3 Additional Sample Modules
|
||||||
- [Story: 8.3 - Additional Services](./8.3-additional-modules.md)
|
- [Story: 8.3 - Additional Modules](./8.3-additional-modules.md)
|
||||||
- **Goal:** Create additional sample feature services to demonstrate different use cases and patterns.
|
- **Goal:** Create additional sample modules to demonstrate different use cases.
|
||||||
- **Deliverables:** Notification Service, Analytics Service (as independent services with their own entry points)
|
- **Deliverables:** Notification module, Analytics module
|
||||||
|
|
||||||
### 8.4 Final Polish and Optimization
|
### 8.4 Final Polish and Optimization
|
||||||
- [Story: 8.4 - Final Polish](./8.4-final-polish.md)
|
- [Story: 8.4 - Final Polish](./8.4-final-polish.md)
|
||||||
|
|||||||
@@ -95,7 +95,6 @@ nav:
|
|||||||
- "Epic 0: Project Setup & Foundation":
|
- "Epic 0: Project Setup & Foundation":
|
||||||
- "ADR-0001: Go Module Path": adr/0001-go-module-path.md
|
- "ADR-0001: Go Module Path": adr/0001-go-module-path.md
|
||||||
- "ADR-0002: Go Version": adr/0002-go-version.md
|
- "ADR-0002: Go Version": adr/0002-go-version.md
|
||||||
- "ADR-0034: Go Version Upgrade to 1.25.3": adr/0034-go-version-upgrade.md
|
|
||||||
- "ADR-0003: Dependency Injection Framework": adr/0003-dependency-injection-framework.md
|
- "ADR-0003: Dependency Injection Framework": adr/0003-dependency-injection-framework.md
|
||||||
- "ADR-0004: Configuration Management": adr/0004-configuration-management.md
|
- "ADR-0004: Configuration Management": adr/0004-configuration-management.md
|
||||||
- "ADR-0005: Logging Framework": adr/0005-logging-framework.md
|
- "ADR-0005: Logging Framework": adr/0005-logging-framework.md
|
||||||
|
|||||||
3
ent/generate.go
Normal file
3
ent/generate.go
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
package ent
|
||||||
|
|
||||||
|
//go:generate go run -mod=mod entgo.io/ent/cmd/ent generate ./schema
|
||||||
@@ -1,59 +0,0 @@
|
|||||||
// Package schema defines the Ent schema for domain entities.
|
|
||||||
package schema
|
|
||||||
|
|
||||||
import (
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"entgo.io/ent"
|
|
||||||
"entgo.io/ent/schema/field"
|
|
||||||
"entgo.io/ent/schema/index"
|
|
||||||
)
|
|
||||||
|
|
||||||
// AuditLog holds the schema definition for the AuditLog entity.
|
|
||||||
type AuditLog struct {
|
|
||||||
ent.Schema
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fields of the AuditLog.
|
|
||||||
func (AuditLog) Fields() []ent.Field {
|
|
||||||
return []ent.Field{
|
|
||||||
field.String("id").
|
|
||||||
Unique().
|
|
||||||
Immutable(),
|
|
||||||
field.String("user_id").
|
|
||||||
NotEmpty().
|
|
||||||
Comment("ID of the user/actor performing the action"),
|
|
||||||
field.String("action").
|
|
||||||
NotEmpty().
|
|
||||||
Comment("Action performed (e.g., user.create, user.update)"),
|
|
||||||
field.String("resource").
|
|
||||||
Optional().
|
|
||||||
Comment("Resource type (e.g., user, role)"),
|
|
||||||
field.String("resource_id").
|
|
||||||
Optional().
|
|
||||||
Comment("ID of the target resource"),
|
|
||||||
field.String("ip_address").
|
|
||||||
Optional().
|
|
||||||
Comment("IP address of the client"),
|
|
||||||
field.String("user_agent").
|
|
||||||
Optional().
|
|
||||||
Comment("User agent of the client"),
|
|
||||||
field.JSON("metadata", map[string]interface{}{}).
|
|
||||||
Optional().
|
|
||||||
Comment("Additional metadata as JSON"),
|
|
||||||
field.Time("timestamp").
|
|
||||||
Default(time.Now).
|
|
||||||
Immutable(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Indexes of the AuditLog.
|
|
||||||
func (AuditLog) Indexes() []ent.Index {
|
|
||||||
return []ent.Index{
|
|
||||||
index.Fields("user_id"),
|
|
||||||
index.Fields("resource_id"),
|
|
||||||
index.Fields("timestamp"),
|
|
||||||
index.Fields("action"),
|
|
||||||
index.Fields("resource"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
18
ent/schema/auditlog.go
Normal file
18
ent/schema/auditlog.go
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
package schema
|
||||||
|
|
||||||
|
import "entgo.io/ent"
|
||||||
|
|
||||||
|
// AuditLog holds the schema definition for the AuditLog entity.
|
||||||
|
type AuditLog struct {
|
||||||
|
ent.Schema
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fields of the AuditLog.
|
||||||
|
func (AuditLog) Fields() []ent.Field {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Edges of the AuditLog.
|
||||||
|
func (AuditLog) Edges() []ent.Edge {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
@@ -1,10 +1,6 @@
|
|||||||
package schema
|
package schema
|
||||||
|
|
||||||
import (
|
import "entgo.io/ent"
|
||||||
"entgo.io/ent"
|
|
||||||
"entgo.io/ent/schema/edge"
|
|
||||||
"entgo.io/ent/schema/field"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Permission holds the schema definition for the Permission entity.
|
// Permission holds the schema definition for the Permission entity.
|
||||||
type Permission struct {
|
type Permission struct {
|
||||||
@@ -13,20 +9,10 @@ type Permission struct {
|
|||||||
|
|
||||||
// Fields of the Permission.
|
// Fields of the Permission.
|
||||||
func (Permission) Fields() []ent.Field {
|
func (Permission) Fields() []ent.Field {
|
||||||
return []ent.Field{
|
return nil
|
||||||
field.String("id").
|
|
||||||
Unique().
|
|
||||||
Immutable(),
|
|
||||||
field.String("name").
|
|
||||||
Unique().
|
|
||||||
NotEmpty().
|
|
||||||
Comment("Format: module.resource.action"),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Edges of the Permission.
|
// Edges of the Permission.
|
||||||
func (Permission) Edges() []ent.Edge {
|
func (Permission) Edges() []ent.Edge {
|
||||||
return []ent.Edge{
|
return nil
|
||||||
edge.To("role_permissions", RolePermission.Type),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,44 +0,0 @@
|
|||||||
package schema
|
|
||||||
|
|
||||||
import (
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"entgo.io/ent"
|
|
||||||
"entgo.io/ent/schema/field"
|
|
||||||
"entgo.io/ent/schema/index"
|
|
||||||
)
|
|
||||||
|
|
||||||
// RefreshToken holds the schema definition for the RefreshToken entity.
|
|
||||||
type RefreshToken struct {
|
|
||||||
ent.Schema
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fields of the RefreshToken.
|
|
||||||
func (RefreshToken) Fields() []ent.Field {
|
|
||||||
return []ent.Field{
|
|
||||||
field.String("id").
|
|
||||||
Unique().
|
|
||||||
Immutable(),
|
|
||||||
field.String("user_id").
|
|
||||||
NotEmpty().
|
|
||||||
Comment("ID of the user who owns this refresh token"),
|
|
||||||
field.String("token_hash").
|
|
||||||
NotEmpty().
|
|
||||||
Sensitive().
|
|
||||||
Comment("SHA256 hash of the refresh token"),
|
|
||||||
field.Time("expires_at").
|
|
||||||
Comment("When the refresh token expires"),
|
|
||||||
field.Time("created_at").
|
|
||||||
Default(time.Now).
|
|
||||||
Immutable(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Indexes of the RefreshToken.
|
|
||||||
func (RefreshToken) Indexes() []ent.Index {
|
|
||||||
return []ent.Index{
|
|
||||||
index.Fields("user_id"),
|
|
||||||
index.Fields("token_hash"),
|
|
||||||
index.Fields("expires_at"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,12 +1,6 @@
|
|||||||
package schema
|
package schema
|
||||||
|
|
||||||
import (
|
import "entgo.io/ent"
|
||||||
"time"
|
|
||||||
|
|
||||||
"entgo.io/ent"
|
|
||||||
"entgo.io/ent/schema/edge"
|
|
||||||
"entgo.io/ent/schema/field"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Role holds the schema definition for the Role entity.
|
// Role holds the schema definition for the Role entity.
|
||||||
type Role struct {
|
type Role struct {
|
||||||
@@ -15,25 +9,10 @@ type Role struct {
|
|||||||
|
|
||||||
// Fields of the Role.
|
// Fields of the Role.
|
||||||
func (Role) Fields() []ent.Field {
|
func (Role) Fields() []ent.Field {
|
||||||
return []ent.Field{
|
return nil
|
||||||
field.String("id").
|
|
||||||
Unique().
|
|
||||||
Immutable(),
|
|
||||||
field.String("name").
|
|
||||||
Unique().
|
|
||||||
NotEmpty(),
|
|
||||||
field.String("description").
|
|
||||||
Optional(),
|
|
||||||
field.Time("created_at").
|
|
||||||
Default(time.Now).
|
|
||||||
Immutable(),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Edges of the Role.
|
// Edges of the Role.
|
||||||
func (Role) Edges() []ent.Edge {
|
func (Role) Edges() []ent.Edge {
|
||||||
return []ent.Edge{
|
return nil
|
||||||
edge.To("role_permissions", RolePermission.Type),
|
|
||||||
edge.To("user_roles", UserRole.Type),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,34 +0,0 @@
|
|||||||
package schema
|
|
||||||
|
|
||||||
import (
|
|
||||||
"entgo.io/ent"
|
|
||||||
"entgo.io/ent/schema/edge"
|
|
||||||
"entgo.io/ent/schema/field"
|
|
||||||
)
|
|
||||||
|
|
||||||
// RolePermission holds the schema definition for the RolePermission entity (many-to-many relationship).
|
|
||||||
type RolePermission struct {
|
|
||||||
ent.Schema
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fields of the RolePermission.
|
|
||||||
func (RolePermission) Fields() []ent.Field {
|
|
||||||
return []ent.Field{
|
|
||||||
field.String("role_id"),
|
|
||||||
field.String("permission_id"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Edges of the RolePermission.
|
|
||||||
func (RolePermission) Edges() []ent.Edge {
|
|
||||||
return []ent.Edge{
|
|
||||||
edge.To("role", Role.Type).
|
|
||||||
Unique().
|
|
||||||
Required().
|
|
||||||
Field("role_id"),
|
|
||||||
edge.To("permission", Permission.Type).
|
|
||||||
Unique().
|
|
||||||
Required().
|
|
||||||
Field("permission_id"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,12 +1,6 @@
|
|||||||
package schema
|
package schema
|
||||||
|
|
||||||
import (
|
import "entgo.io/ent"
|
||||||
"time"
|
|
||||||
|
|
||||||
"entgo.io/ent"
|
|
||||||
"entgo.io/ent/schema/edge"
|
|
||||||
"entgo.io/ent/schema/field"
|
|
||||||
)
|
|
||||||
|
|
||||||
// User holds the schema definition for the User entity.
|
// User holds the schema definition for the User entity.
|
||||||
type User struct {
|
type User struct {
|
||||||
@@ -15,43 +9,10 @@ type User struct {
|
|||||||
|
|
||||||
// Fields of the User.
|
// Fields of the User.
|
||||||
func (User) Fields() []ent.Field {
|
func (User) Fields() []ent.Field {
|
||||||
return []ent.Field{
|
return nil
|
||||||
field.String("id").
|
|
||||||
Unique().
|
|
||||||
Immutable(),
|
|
||||||
field.String("email").
|
|
||||||
Unique().
|
|
||||||
NotEmpty(),
|
|
||||||
field.String("username").
|
|
||||||
Optional(),
|
|
||||||
field.String("first_name").
|
|
||||||
Optional(),
|
|
||||||
field.String("last_name").
|
|
||||||
Optional(),
|
|
||||||
field.String("password_hash").
|
|
||||||
NotEmpty(),
|
|
||||||
field.Bool("verified").
|
|
||||||
Default(false),
|
|
||||||
field.String("email_verification_token").
|
|
||||||
Optional().
|
|
||||||
Sensitive(),
|
|
||||||
field.String("password_reset_token").
|
|
||||||
Optional().
|
|
||||||
Sensitive(),
|
|
||||||
field.Time("password_reset_expires_at").
|
|
||||||
Optional(),
|
|
||||||
field.Time("created_at").
|
|
||||||
Default(time.Now).
|
|
||||||
Immutable(),
|
|
||||||
field.Time("updated_at").
|
|
||||||
Default(time.Now).
|
|
||||||
UpdateDefault(time.Now),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Edges of the User.
|
// Edges of the User.
|
||||||
func (User) Edges() []ent.Edge {
|
func (User) Edges() []ent.Edge {
|
||||||
return []ent.Edge{
|
return nil
|
||||||
edge.To("user_roles", UserRole.Type),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,34 +0,0 @@
|
|||||||
package schema
|
|
||||||
|
|
||||||
import (
|
|
||||||
"entgo.io/ent"
|
|
||||||
"entgo.io/ent/schema/edge"
|
|
||||||
"entgo.io/ent/schema/field"
|
|
||||||
)
|
|
||||||
|
|
||||||
// UserRole holds the schema definition for the UserRole entity (many-to-many relationship).
|
|
||||||
type UserRole struct {
|
|
||||||
ent.Schema
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fields of the UserRole.
|
|
||||||
func (UserRole) Fields() []ent.Field {
|
|
||||||
return []ent.Field{
|
|
||||||
field.String("user_id"),
|
|
||||||
field.String("role_id"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Edges of the UserRole.
|
|
||||||
func (UserRole) Edges() []ent.Edge {
|
|
||||||
return []ent.Edge{
|
|
||||||
edge.To("user", User.Type).
|
|
||||||
Unique().
|
|
||||||
Required().
|
|
||||||
Field("user_id"),
|
|
||||||
edge.To("role", Role.Type).
|
|
||||||
Unique().
|
|
||||||
Required().
|
|
||||||
Field("role_id"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
36
go.mod
36
go.mod
@@ -1,16 +1,14 @@
|
|||||||
module git.dcentral.systems/toolz/goplt
|
module git.dcentral.systems/toolz/goplt
|
||||||
|
|
||||||
go 1.25.3
|
go 1.24
|
||||||
|
|
||||||
require (
|
require (
|
||||||
entgo.io/ent v0.14.5
|
entgo.io/ent v0.14.5
|
||||||
github.com/gin-gonic/gin v1.10.1
|
github.com/gin-gonic/gin v1.10.1
|
||||||
github.com/google/uuid v1.6.0
|
github.com/google/uuid v1.6.0
|
||||||
github.com/hashicorp/consul/api v1.33.0
|
|
||||||
github.com/lib/pq v1.10.9
|
github.com/lib/pq v1.10.9
|
||||||
github.com/prometheus/client_golang v1.23.2
|
github.com/prometheus/client_golang v1.23.2
|
||||||
github.com/spf13/viper v1.18.0
|
github.com/spf13/viper v1.18.0
|
||||||
github.com/stretchr/testify v1.11.1
|
|
||||||
go.opentelemetry.io/contrib/instrumentation/github.com/gin-gonic/gin/otelgin v0.63.0
|
go.opentelemetry.io/contrib/instrumentation/github.com/gin-gonic/gin/otelgin v0.63.0
|
||||||
go.opentelemetry.io/otel v1.38.0
|
go.opentelemetry.io/otel v1.38.0
|
||||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.38.0
|
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.38.0
|
||||||
@@ -19,16 +17,12 @@ require (
|
|||||||
go.opentelemetry.io/otel/trace v1.38.0
|
go.opentelemetry.io/otel/trace v1.38.0
|
||||||
go.uber.org/fx v1.24.0
|
go.uber.org/fx v1.24.0
|
||||||
go.uber.org/zap v1.26.0
|
go.uber.org/zap v1.26.0
|
||||||
golang.org/x/crypto v0.43.0
|
|
||||||
google.golang.org/grpc v1.75.0
|
|
||||||
google.golang.org/protobuf v1.36.8
|
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
ariga.io/atlas v0.32.1-0.20250325101103-175b25e1c1b9 // indirect
|
ariga.io/atlas v0.32.1-0.20250325101103-175b25e1c1b9 // indirect
|
||||||
github.com/agext/levenshtein v1.2.3 // indirect
|
github.com/agext/levenshtein v1.2.3 // indirect
|
||||||
github.com/apparentlymart/go-textseg/v15 v15.0.0 // indirect
|
github.com/apparentlymart/go-textseg/v15 v15.0.0 // indirect
|
||||||
github.com/armon/go-metrics v0.4.1 // indirect
|
|
||||||
github.com/beorn7/perks v1.0.1 // indirect
|
github.com/beorn7/perks v1.0.1 // indirect
|
||||||
github.com/bmatcuk/doublestar v1.3.4 // indirect
|
github.com/bmatcuk/doublestar v1.3.4 // indirect
|
||||||
github.com/bytedance/sonic v1.14.0 // indirect
|
github.com/bytedance/sonic v1.14.0 // indirect
|
||||||
@@ -36,8 +30,6 @@ require (
|
|||||||
github.com/cenkalti/backoff/v5 v5.0.3 // indirect
|
github.com/cenkalti/backoff/v5 v5.0.3 // indirect
|
||||||
github.com/cespare/xxhash/v2 v2.3.0 // indirect
|
github.com/cespare/xxhash/v2 v2.3.0 // indirect
|
||||||
github.com/cloudwego/base64x v0.1.6 // indirect
|
github.com/cloudwego/base64x v0.1.6 // indirect
|
||||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
|
|
||||||
github.com/fatih/color v1.16.0 // indirect
|
|
||||||
github.com/fsnotify/fsnotify v1.7.0 // indirect
|
github.com/fsnotify/fsnotify v1.7.0 // indirect
|
||||||
github.com/gabriel-vasile/mimetype v1.4.10 // indirect
|
github.com/gabriel-vasile/mimetype v1.4.10 // indirect
|
||||||
github.com/gin-contrib/sse v1.1.0 // indirect
|
github.com/gin-contrib/sse v1.1.0 // indirect
|
||||||
@@ -47,35 +39,22 @@ require (
|
|||||||
github.com/go-playground/locales v0.14.1 // indirect
|
github.com/go-playground/locales v0.14.1 // indirect
|
||||||
github.com/go-playground/universal-translator v0.18.1 // indirect
|
github.com/go-playground/universal-translator v0.18.1 // indirect
|
||||||
github.com/go-playground/validator/v10 v10.27.0 // indirect
|
github.com/go-playground/validator/v10 v10.27.0 // indirect
|
||||||
github.com/go-viper/mapstructure/v2 v2.4.0 // indirect
|
|
||||||
github.com/goccy/go-json v0.10.5 // indirect
|
github.com/goccy/go-json v0.10.5 // indirect
|
||||||
github.com/golang-jwt/jwt/v5 v5.3.0 // indirect
|
|
||||||
github.com/google/go-cmp v0.7.0 // indirect
|
github.com/google/go-cmp v0.7.0 // indirect
|
||||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.2 // indirect
|
github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.2 // indirect
|
||||||
github.com/hashicorp/errwrap v1.1.0 // indirect
|
|
||||||
github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
|
|
||||||
github.com/hashicorp/go-hclog v1.5.0 // indirect
|
|
||||||
github.com/hashicorp/go-immutable-radix v1.3.1 // indirect
|
|
||||||
github.com/hashicorp/go-multierror v1.1.1 // indirect
|
|
||||||
github.com/hashicorp/go-rootcerts v1.0.2 // indirect
|
|
||||||
github.com/hashicorp/golang-lru v0.5.4 // indirect
|
|
||||||
github.com/hashicorp/hcl v1.0.0 // indirect
|
github.com/hashicorp/hcl v1.0.0 // indirect
|
||||||
github.com/hashicorp/hcl/v2 v2.18.1 // indirect
|
github.com/hashicorp/hcl/v2 v2.18.1 // indirect
|
||||||
github.com/hashicorp/serf v0.10.1 // indirect
|
|
||||||
github.com/json-iterator/go v1.1.12 // indirect
|
github.com/json-iterator/go v1.1.12 // indirect
|
||||||
github.com/klauspost/cpuid/v2 v2.3.0 // indirect
|
github.com/klauspost/cpuid/v2 v2.3.0 // indirect
|
||||||
github.com/leodido/go-urn v1.4.0 // indirect
|
github.com/leodido/go-urn v1.4.0 // indirect
|
||||||
github.com/magiconair/properties v1.8.7 // indirect
|
github.com/magiconair/properties v1.8.7 // indirect
|
||||||
github.com/mattn/go-colorable v0.1.13 // indirect
|
|
||||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||||
github.com/mitchellh/go-homedir v1.1.0 // indirect
|
|
||||||
github.com/mitchellh/go-wordwrap v1.0.1 // indirect
|
github.com/mitchellh/go-wordwrap v1.0.1 // indirect
|
||||||
github.com/mitchellh/mapstructure v1.5.0 // indirect
|
github.com/mitchellh/mapstructure v1.5.0 // indirect
|
||||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
|
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
|
||||||
github.com/pelletier/go-toml/v2 v2.2.4 // indirect
|
github.com/pelletier/go-toml/v2 v2.2.4 // indirect
|
||||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
|
|
||||||
github.com/prometheus/client_model v0.6.2 // indirect
|
github.com/prometheus/client_model v0.6.2 // indirect
|
||||||
github.com/prometheus/common v0.66.1 // indirect
|
github.com/prometheus/common v0.66.1 // indirect
|
||||||
github.com/prometheus/procfs v0.16.1 // indirect
|
github.com/prometheus/procfs v0.16.1 // indirect
|
||||||
@@ -98,13 +77,16 @@ require (
|
|||||||
go.uber.org/multierr v1.10.0 // indirect
|
go.uber.org/multierr v1.10.0 // indirect
|
||||||
go.yaml.in/yaml/v2 v2.4.2 // indirect
|
go.yaml.in/yaml/v2 v2.4.2 // indirect
|
||||||
golang.org/x/arch v0.20.0 // indirect
|
golang.org/x/arch v0.20.0 // indirect
|
||||||
golang.org/x/exp v0.0.0-20250808145144-a408d31f581a // indirect
|
golang.org/x/crypto v0.41.0 // indirect
|
||||||
golang.org/x/mod v0.28.0 // indirect
|
golang.org/x/exp v0.0.0-20230905200255-921286631fa9 // indirect
|
||||||
golang.org/x/net v0.45.0 // indirect
|
golang.org/x/mod v0.26.0 // indirect
|
||||||
golang.org/x/sys v0.37.0 // indirect
|
golang.org/x/net v0.43.0 // indirect
|
||||||
golang.org/x/text v0.30.0 // indirect
|
golang.org/x/sys v0.35.0 // indirect
|
||||||
|
golang.org/x/text v0.28.0 // indirect
|
||||||
google.golang.org/genproto/googleapis/api v0.0.0-20250825161204-c5933d9347a5 // indirect
|
google.golang.org/genproto/googleapis/api v0.0.0-20250825161204-c5933d9347a5 // indirect
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250825161204-c5933d9347a5 // indirect
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20250825161204-c5933d9347a5 // indirect
|
||||||
|
google.golang.org/grpc v1.75.0 // indirect
|
||||||
|
google.golang.org/protobuf v1.36.8 // indirect
|
||||||
gopkg.in/ini.v1 v1.67.0 // indirect
|
gopkg.in/ini.v1 v1.67.0 // indirect
|
||||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||||
)
|
)
|
||||||
|
|||||||
232
go.sum
232
go.sum
@@ -4,26 +4,12 @@ entgo.io/ent v0.14.5 h1:Rj2WOYJtCkWyFo6a+5wB3EfBRP0rnx1fMk6gGA0UUe4=
|
|||||||
entgo.io/ent v0.14.5/go.mod h1:zTzLmWtPvGpmSwtkaayM2cm5m819NdM7z7tYPq3vN0U=
|
entgo.io/ent v0.14.5/go.mod h1:zTzLmWtPvGpmSwtkaayM2cm5m819NdM7z7tYPq3vN0U=
|
||||||
github.com/DATA-DOG/go-sqlmock v1.5.0 h1:Shsta01QNfFxHCfpW6YH2STWB0MudeXXEWMr20OEh60=
|
github.com/DATA-DOG/go-sqlmock v1.5.0 h1:Shsta01QNfFxHCfpW6YH2STWB0MudeXXEWMr20OEh60=
|
||||||
github.com/DATA-DOG/go-sqlmock v1.5.0/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q5eFN3EC/SaM=
|
github.com/DATA-DOG/go-sqlmock v1.5.0/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q5eFN3EC/SaM=
|
||||||
github.com/DataDog/datadog-go v3.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ=
|
|
||||||
github.com/agext/levenshtein v1.2.3 h1:YB2fHEn0UJagG8T1rrWknE3ZQzWM06O8AMAatNn7lmo=
|
github.com/agext/levenshtein v1.2.3 h1:YB2fHEn0UJagG8T1rrWknE3ZQzWM06O8AMAatNn7lmo=
|
||||||
github.com/agext/levenshtein v1.2.3/go.mod h1:JEDfjyjHDjOF/1e4FlBE/PkbqA9OfWu2ki2W0IB5558=
|
github.com/agext/levenshtein v1.2.3/go.mod h1:JEDfjyjHDjOF/1e4FlBE/PkbqA9OfWu2ki2W0IB5558=
|
||||||
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
|
|
||||||
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
|
|
||||||
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
|
|
||||||
github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
|
|
||||||
github.com/apparentlymart/go-textseg/v15 v15.0.0 h1:uYvfpb3DyLSCGWnctWKGj857c6ew1u1fNQOlOtuGxQY=
|
github.com/apparentlymart/go-textseg/v15 v15.0.0 h1:uYvfpb3DyLSCGWnctWKGj857c6ew1u1fNQOlOtuGxQY=
|
||||||
github.com/apparentlymart/go-textseg/v15 v15.0.0/go.mod h1:K8XmNZdhEBkdlyDdvbmmsvpAG721bKi0joRfFdHIWJ4=
|
github.com/apparentlymart/go-textseg/v15 v15.0.0/go.mod h1:K8XmNZdhEBkdlyDdvbmmsvpAG721bKi0joRfFdHIWJ4=
|
||||||
github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o=
|
|
||||||
github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY=
|
|
||||||
github.com/armon/go-metrics v0.4.1 h1:hR91U9KYmb6bLBYLQjyM+3j+rcd/UhE+G78SFnF8gJA=
|
|
||||||
github.com/armon/go-metrics v0.4.1/go.mod h1:E6amYzXo6aW1tqzoZGT755KkbgrJsSdpwZ+3JqfkOG4=
|
|
||||||
github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
|
|
||||||
github.com/armon/go-radix v1.0.0/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
|
|
||||||
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
|
|
||||||
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
|
|
||||||
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
|
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
|
||||||
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
|
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
|
||||||
github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
|
|
||||||
github.com/bmatcuk/doublestar v1.3.4 h1:gPypJ5xD31uhX6Tf54sDPUOBXTqKH4c9aPY66CyQrS0=
|
github.com/bmatcuk/doublestar v1.3.4 h1:gPypJ5xD31uhX6Tf54sDPUOBXTqKH4c9aPY66CyQrS0=
|
||||||
github.com/bmatcuk/doublestar v1.3.4/go.mod h1:wiQtGV+rzVYxB7WIlirSN++5HPtPlXEo9MEoZQC/PmE=
|
github.com/bmatcuk/doublestar v1.3.4/go.mod h1:wiQtGV+rzVYxB7WIlirSN++5HPtPlXEo9MEoZQC/PmE=
|
||||||
github.com/bytedance/sonic v1.14.0 h1:/OfKt8HFw0kh2rj8N0F6C/qPGRESq0BbaNZgcNXXzQQ=
|
github.com/bytedance/sonic v1.14.0 h1:/OfKt8HFw0kh2rj8N0F6C/qPGRESq0BbaNZgcNXXzQQ=
|
||||||
@@ -32,22 +18,14 @@ github.com/bytedance/sonic/loader v0.3.0 h1:dskwH8edlzNMctoruo8FPTJDF3vLtDT0sXZw
|
|||||||
github.com/bytedance/sonic/loader v0.3.0/go.mod h1:N8A3vUdtUebEY2/VQC0MyhYeKUFosQU6FxH2JmUe6VI=
|
github.com/bytedance/sonic/loader v0.3.0/go.mod h1:N8A3vUdtUebEY2/VQC0MyhYeKUFosQU6FxH2JmUe6VI=
|
||||||
github.com/cenkalti/backoff/v5 v5.0.3 h1:ZN+IMa753KfX5hd8vVaMixjnqRZ3y8CuJKRKj1xcsSM=
|
github.com/cenkalti/backoff/v5 v5.0.3 h1:ZN+IMa753KfX5hd8vVaMixjnqRZ3y8CuJKRKj1xcsSM=
|
||||||
github.com/cenkalti/backoff/v5 v5.0.3/go.mod h1:rkhZdG3JZukswDf7f0cwqPNk4K0sa+F97BxZthm/crw=
|
github.com/cenkalti/backoff/v5 v5.0.3/go.mod h1:rkhZdG3JZukswDf7f0cwqPNk4K0sa+F97BxZthm/crw=
|
||||||
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
|
||||||
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
|
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
|
||||||
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||||
github.com/circonus-labs/circonus-gometrics v2.3.1+incompatible/go.mod h1:nmEj6Dob7S7YxXgwXpfOuvO54S+tGdZdw9fuRZt25Ag=
|
|
||||||
github.com/circonus-labs/circonusllhist v0.1.3/go.mod h1:kMXHVDlOchFAehlya5ePtbp5jckzBHf4XRpQvBOLI+I=
|
|
||||||
github.com/cloudwego/base64x v0.1.6 h1:t11wG9AECkCDk5fMSoxmufanudBtJ+/HemLstXDLI2M=
|
github.com/cloudwego/base64x v0.1.6 h1:t11wG9AECkCDk5fMSoxmufanudBtJ+/HemLstXDLI2M=
|
||||||
github.com/cloudwego/base64x v0.1.6/go.mod h1:OFcloc187FXDaYHvrNIjxSe8ncn0OOM8gEHfghB2IPU=
|
github.com/cloudwego/base64x v0.1.6/go.mod h1:OFcloc187FXDaYHvrNIjxSe8ncn0OOM8gEHfghB2IPU=
|
||||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
|
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
|
||||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
|
|
||||||
github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU=
|
|
||||||
github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk=
|
|
||||||
github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM=
|
|
||||||
github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE=
|
|
||||||
github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=
|
github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=
|
||||||
github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
|
github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
|
||||||
github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA=
|
github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA=
|
||||||
@@ -58,10 +36,6 @@ github.com/gin-contrib/sse v1.1.0 h1:n0w2GMuUpWDVp7qSpvze6fAu9iRxJY4Hmj6AmBOU05w
|
|||||||
github.com/gin-contrib/sse v1.1.0/go.mod h1:hxRZ5gVpWMT7Z0B0gSNYqqsSCNIJMjzvm6fqCz9vjwM=
|
github.com/gin-contrib/sse v1.1.0/go.mod h1:hxRZ5gVpWMT7Z0B0gSNYqqsSCNIJMjzvm6fqCz9vjwM=
|
||||||
github.com/gin-gonic/gin v1.10.1 h1:T0ujvqyCSqRopADpgPgiTT63DUQVSfojyME59Ei63pQ=
|
github.com/gin-gonic/gin v1.10.1 h1:T0ujvqyCSqRopADpgPgiTT63DUQVSfojyME59Ei63pQ=
|
||||||
github.com/gin-gonic/gin v1.10.1/go.mod h1:4PMNQiOhvDRa013RKVbsiNwoyezlm2rm0uX/T7kzp5Y=
|
github.com/gin-gonic/gin v1.10.1/go.mod h1:4PMNQiOhvDRa013RKVbsiNwoyezlm2rm0uX/T7kzp5Y=
|
||||||
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
|
|
||||||
github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
|
|
||||||
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
|
|
||||||
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
|
|
||||||
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
|
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
|
||||||
github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
|
github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
|
||||||
github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
||||||
@@ -77,26 +51,12 @@ github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJn
|
|||||||
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
|
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
|
||||||
github.com/go-playground/validator/v10 v10.27.0 h1:w8+XrWVMhGkxOaaowyKH35gFydVHOvC0/uWoy2Fzwn4=
|
github.com/go-playground/validator/v10 v10.27.0 h1:w8+XrWVMhGkxOaaowyKH35gFydVHOvC0/uWoy2Fzwn4=
|
||||||
github.com/go-playground/validator/v10 v10.27.0/go.mod h1:I5QpIEbmr8On7W0TktmJAumgzX4CA1XNl4ZmDuVHKKo=
|
github.com/go-playground/validator/v10 v10.27.0/go.mod h1:I5QpIEbmr8On7W0TktmJAumgzX4CA1XNl4ZmDuVHKKo=
|
||||||
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
|
|
||||||
github.com/go-test/deep v1.0.3 h1:ZrJSEWsXzPOxaZnFteGEfooLba+ju3FYIbOrS+rQd68=
|
github.com/go-test/deep v1.0.3 h1:ZrJSEWsXzPOxaZnFteGEfooLba+ju3FYIbOrS+rQd68=
|
||||||
github.com/go-test/deep v1.0.3/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA=
|
github.com/go-test/deep v1.0.3/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA=
|
||||||
github.com/go-viper/mapstructure/v2 v2.4.0 h1:EBsztssimR/CONLSZZ04E8qAkxNYq4Qp9LvH92wZUgs=
|
|
||||||
github.com/go-viper/mapstructure/v2 v2.4.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
|
|
||||||
github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4=
|
github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4=
|
||||||
github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
|
github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
|
||||||
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
|
|
||||||
github.com/golang-jwt/jwt/v5 v5.3.0 h1:pv4AsKCKKZuqlgs5sUmn4x8UlGa0kEVt/puTpKx9vvo=
|
|
||||||
github.com/golang-jwt/jwt/v5 v5.3.0/go.mod h1:fxCRLWMO43lRc8nhHWY6LGqRcf+1gQWArsqaEUEa5bE=
|
|
||||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
|
||||||
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
|
||||||
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
|
||||||
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
|
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
|
||||||
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
|
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
|
||||||
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
|
||||||
github.com/google/btree v1.0.1 h1:gK4Kx5IaGY9CD5sPJ36FHiBJ6ZXl0kilRiiCj+jdYp4=
|
|
||||||
github.com/google/btree v1.0.1/go.mod h1:xXMiIv4Fb/0kKde4SpL7qlzvu5cMJDRkFDxJfI9uaxA=
|
|
||||||
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
|
||||||
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
|
||||||
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
|
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
|
||||||
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
|
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
|
||||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||||
@@ -104,70 +64,18 @@ github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
|||||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.2 h1:8Tjv8EJ+pM1xP8mK6egEbD1OgnVTyacbefKhmbLhIhU=
|
github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.2 h1:8Tjv8EJ+pM1xP8mK6egEbD1OgnVTyacbefKhmbLhIhU=
|
||||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.2/go.mod h1:pkJQ2tZHJ0aFOVEEot6oZmaVEZcRme73eIFmhiVuRWs=
|
github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.2/go.mod h1:pkJQ2tZHJ0aFOVEEot6oZmaVEZcRme73eIFmhiVuRWs=
|
||||||
github.com/hashicorp/consul/api v1.33.0 h1:MnFUzN1Bo6YDGi/EsRLbVNgA4pyCymmcswrE5j4OHBM=
|
|
||||||
github.com/hashicorp/consul/api v1.33.0/go.mod h1:vLz2I/bqqCYiG0qRHGerComvbwSWKswc8rRFtnYBrIw=
|
|
||||||
github.com/hashicorp/consul/sdk v0.17.0 h1:N/JigV6y1yEMfTIhXoW0DXUecM2grQnFuRpY7PcLHLI=
|
|
||||||
github.com/hashicorp/consul/sdk v0.17.0/go.mod h1:8dgIhY6VlPUprRH7o7UenVuFEgq017qUn3k9wS5mCt4=
|
|
||||||
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
|
|
||||||
github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I=
|
|
||||||
github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
|
|
||||||
github.com/hashicorp/go-cleanhttp v0.5.0/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=
|
|
||||||
github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ=
|
|
||||||
github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48=
|
|
||||||
github.com/hashicorp/go-hclog v1.5.0 h1:bI2ocEMgcVlz55Oj1xZNBsVi900c7II+fWDyV9o+13c=
|
|
||||||
github.com/hashicorp/go-hclog v1.5.0/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M=
|
|
||||||
github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60=
|
|
||||||
github.com/hashicorp/go-immutable-radix v1.3.1 h1:DKHmCUm2hRBK510BaiZlwvpD40f8bJFeZnpfm2KLowc=
|
|
||||||
github.com/hashicorp/go-immutable-radix v1.3.1/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60=
|
|
||||||
github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM=
|
|
||||||
github.com/hashicorp/go-msgpack v0.5.5 h1:i9R9JSrqIz0QVLz3sz+i3YJdT7TTSLcfLLzJi9aZTuI=
|
|
||||||
github.com/hashicorp/go-msgpack v0.5.5/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM=
|
|
||||||
github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk=
|
|
||||||
github.com/hashicorp/go-multierror v1.1.0/go.mod h1:spPvp8C1qA32ftKqdAHm4hHTbPw+vmowP0z+KUhOZdA=
|
|
||||||
github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo=
|
|
||||||
github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM=
|
|
||||||
github.com/hashicorp/go-retryablehttp v0.5.3/go.mod h1:9B5zBasrRhHXnJnui7y6sL7es7NDiJgTc6Er0maI1Xs=
|
|
||||||
github.com/hashicorp/go-rootcerts v1.0.2 h1:jzhAVGtqPKbwpyCPELlgNWhE1znq+qwJtW5Oi2viEzc=
|
|
||||||
github.com/hashicorp/go-rootcerts v1.0.2/go.mod h1:pqUvnprVnM5bf7AOirdbb01K4ccR319Vf4pU3K5EGc8=
|
|
||||||
github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU=
|
|
||||||
github.com/hashicorp/go-sockaddr v1.0.2 h1:ztczhD1jLxIRjVejw8gFomI1BQZOe2WoVOu0SyteCQc=
|
|
||||||
github.com/hashicorp/go-sockaddr v1.0.2/go.mod h1:rB4wwRAUzs07qva3c5SdrY/NEtAUjGlgmH/UkBUC97A=
|
|
||||||
github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4=
|
|
||||||
github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
|
|
||||||
github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
|
|
||||||
github.com/hashicorp/go-uuid v1.0.3 h1:2gKiV6YVmrJ1i2CKKa9obLvRieoRGviZFL26PcT/Co8=
|
|
||||||
github.com/hashicorp/go-uuid v1.0.3/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
|
|
||||||
github.com/hashicorp/go-version v1.2.1 h1:zEfKbn2+PDgroKdiOzqiE8rsmLqU2uwi5PB5pBJ3TkI=
|
|
||||||
github.com/hashicorp/go-version v1.2.1/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
|
|
||||||
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
|
||||||
github.com/hashicorp/golang-lru v0.5.4 h1:YDjusn29QI/Das2iO9M0BHnIbxPeyuCHsjMW+lJfyTc=
|
|
||||||
github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4=
|
|
||||||
github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
|
github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
|
||||||
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
|
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
|
||||||
github.com/hashicorp/hcl/v2 v2.18.1 h1:6nxnOJFku1EuSawSD81fuviYUV8DxFr3fp2dUi3ZYSo=
|
github.com/hashicorp/hcl/v2 v2.18.1 h1:6nxnOJFku1EuSawSD81fuviYUV8DxFr3fp2dUi3ZYSo=
|
||||||
github.com/hashicorp/hcl/v2 v2.18.1/go.mod h1:ThLC89FV4p9MPW804KVbe/cEXoQ8NZEh+JtMeeGErHE=
|
github.com/hashicorp/hcl/v2 v2.18.1/go.mod h1:ThLC89FV4p9MPW804KVbe/cEXoQ8NZEh+JtMeeGErHE=
|
||||||
github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64=
|
|
||||||
github.com/hashicorp/mdns v1.0.4/go.mod h1:mtBihi+LeNXGtG8L9dX59gAEa12BDtBQSp4v/YAJqrc=
|
|
||||||
github.com/hashicorp/memberlist v0.5.0 h1:EtYPN8DpAURiapus508I4n9CzHs2W+8NZGbmmR/prTM=
|
|
||||||
github.com/hashicorp/memberlist v0.5.0/go.mod h1:yvyXLpo0QaGE59Y7hDTsTzDD25JYBZ4mHgHUZ8lrOI0=
|
|
||||||
github.com/hashicorp/serf v0.10.1 h1:Z1H2J60yRKvfDYAOZLd2MU0ND4AH/WDz7xYHDWQsIPY=
|
|
||||||
github.com/hashicorp/serf v0.10.1/go.mod h1:yL2t6BqATOLGc5HF7qbFkTfXoPIY0WZdWHfEvMqbG+4=
|
|
||||||
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
|
|
||||||
github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
|
||||||
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
|
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
|
||||||
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
|
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
|
||||||
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
|
|
||||||
github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo=
|
github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo=
|
||||||
github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ=
|
github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ=
|
||||||
github.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y=
|
github.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y=
|
||||||
github.com/klauspost/cpuid/v2 v2.3.0/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0=
|
github.com/klauspost/cpuid/v2 v2.3.0/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0=
|
||||||
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
|
||||||
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
|
|
||||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
|
||||||
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||||
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
||||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
|
||||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
|
||||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||||
github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
|
github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
|
||||||
@@ -178,125 +86,63 @@ github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw=
|
|||||||
github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
|
github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
|
||||||
github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY=
|
github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY=
|
||||||
github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0=
|
github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0=
|
||||||
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
|
|
||||||
github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
|
|
||||||
github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
|
|
||||||
github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
|
|
||||||
github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4=
|
|
||||||
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
|
|
||||||
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
|
|
||||||
github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
|
|
||||||
github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
|
|
||||||
github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE=
|
|
||||||
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
|
|
||||||
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
|
|
||||||
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
|
||||||
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||||
github.com/mattn/go-runewidth v0.0.9 h1:Lm995f3rfxdpd6TSmuVCHVb/QhupuXlYr8sCI/QdE+0=
|
|
||||||
github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
|
|
||||||
github.com/mattn/go-sqlite3 v1.14.17 h1:mCRHCLDUBXgpKAqIKsaAaAsrAlbkeomtRFKXh2L6YIM=
|
github.com/mattn/go-sqlite3 v1.14.17 h1:mCRHCLDUBXgpKAqIKsaAaAsrAlbkeomtRFKXh2L6YIM=
|
||||||
github.com/mattn/go-sqlite3 v1.14.17/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
|
github.com/mattn/go-sqlite3 v1.14.17/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
|
||||||
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
|
|
||||||
github.com/miekg/dns v1.1.26/go.mod h1:bPDLeHnStXmXAq1m/Ch/hvfNHr14JKNPMBo3VZKjuso=
|
|
||||||
github.com/miekg/dns v1.1.41 h1:WMszZWJG0XmzbK9FEmzH2TVcqYzFesusSIB41b8KHxY=
|
|
||||||
github.com/miekg/dns v1.1.41/go.mod h1:p6aan82bvRIyn+zDIv9xYNUpwa73JcSh9BKwknJysuI=
|
|
||||||
github.com/mitchellh/cli v1.1.0/go.mod h1:xcISNoH86gajksDmfB23e/pu+B+GeFRMYmoHXxx3xhI=
|
|
||||||
github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y=
|
|
||||||
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
|
|
||||||
github.com/mitchellh/go-wordwrap v1.0.1 h1:TLuKupo69TCn6TQSyGxwI1EblZZEsQ0vMlAFQflz0v0=
|
github.com/mitchellh/go-wordwrap v1.0.1 h1:TLuKupo69TCn6TQSyGxwI1EblZZEsQ0vMlAFQflz0v0=
|
||||||
github.com/mitchellh/go-wordwrap v1.0.1/go.mod h1:R62XHJLzvMFRBbcrT7m7WgmE1eOyTSsCt+hzestvNj0=
|
github.com/mitchellh/go-wordwrap v1.0.1/go.mod h1:R62XHJLzvMFRBbcrT7m7WgmE1eOyTSsCt+hzestvNj0=
|
||||||
github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
|
|
||||||
github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
|
github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
|
||||||
github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
|
github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
|
||||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
|
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
|
||||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||||
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
|
||||||
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
|
||||||
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
|
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
|
||||||
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
|
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
|
||||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=
|
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=
|
||||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
|
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
|
||||||
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
|
|
||||||
github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec=
|
|
||||||
github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY=
|
|
||||||
github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
|
|
||||||
github.com/pascaldekloe/goe v0.1.0 h1:cBOtyMzM9HTpWjXfbbunk26uA6nG3a8n06Wieeh0MwY=
|
|
||||||
github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
|
|
||||||
github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4=
|
github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4=
|
||||||
github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY=
|
github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY=
|
||||||
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
|
||||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
|
||||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
|
||||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
|
||||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
|
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
|
||||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI=
|
|
||||||
github.com/posener/complete v1.2.3/go.mod h1:WZIdtGGp+qx0sLrYKtIRAruyNpv6hFCicSgv7Sy7s/s=
|
|
||||||
github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
|
|
||||||
github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo=
|
|
||||||
github.com/prometheus/client_golang v1.4.0/go.mod h1:e9GMxYsXl05ICDXkRhurwBS4Q3OK1iX/F2sw+iXX5zU=
|
|
||||||
github.com/prometheus/client_golang v1.23.2 h1:Je96obch5RDVy3FDMndoUsjAhG5Edi49h0RJWRi/o0o=
|
github.com/prometheus/client_golang v1.23.2 h1:Je96obch5RDVy3FDMndoUsjAhG5Edi49h0RJWRi/o0o=
|
||||||
github.com/prometheus/client_golang v1.23.2/go.mod h1:Tb1a6LWHB3/SPIzCoaDXI4I8UHKeFTEQ1YCr+0Gyqmg=
|
github.com/prometheus/client_golang v1.23.2/go.mod h1:Tb1a6LWHB3/SPIzCoaDXI4I8UHKeFTEQ1YCr+0Gyqmg=
|
||||||
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
|
|
||||||
github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
|
||||||
github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
|
||||||
github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk=
|
github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk=
|
||||||
github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE=
|
github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE=
|
||||||
github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
|
|
||||||
github.com/prometheus/common v0.9.1/go.mod h1:yhUN8i9wzaXS3w1O07YhxHEBxD+W35wd8bs7vj7HSQ4=
|
|
||||||
github.com/prometheus/common v0.66.1 h1:h5E0h5/Y8niHc5DlaLlWLArTQI7tMrsfQjHV+d9ZoGs=
|
github.com/prometheus/common v0.66.1 h1:h5E0h5/Y8niHc5DlaLlWLArTQI7tMrsfQjHV+d9ZoGs=
|
||||||
github.com/prometheus/common v0.66.1/go.mod h1:gcaUsgf3KfRSwHY4dIMXLPV0K/Wg1oZ8+SbZk/HH/dA=
|
github.com/prometheus/common v0.66.1/go.mod h1:gcaUsgf3KfRSwHY4dIMXLPV0K/Wg1oZ8+SbZk/HH/dA=
|
||||||
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
|
|
||||||
github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
|
|
||||||
github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A=
|
|
||||||
github.com/prometheus/procfs v0.16.1 h1:hZ15bTNuirocR6u0JZ6BAHHmwS1p8B4P6MRqxtzMyRg=
|
github.com/prometheus/procfs v0.16.1 h1:hZ15bTNuirocR6u0JZ6BAHHmwS1p8B4P6MRqxtzMyRg=
|
||||||
github.com/prometheus/procfs v0.16.1/go.mod h1:teAbpZRB1iIAJYREa1LsoWUXykVXA1KlTmWl8x/U+Is=
|
github.com/prometheus/procfs v0.16.1/go.mod h1:teAbpZRB1iIAJYREa1LsoWUXykVXA1KlTmWl8x/U+Is=
|
||||||
github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII=
|
github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII=
|
||||||
github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o=
|
github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o=
|
||||||
github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
|
|
||||||
github.com/sagikazarmark/locafero v0.4.0 h1:HApY1R9zGo4DBgr7dqsTH/JJxLTTsOt7u6keLGt6kNQ=
|
github.com/sagikazarmark/locafero v0.4.0 h1:HApY1R9zGo4DBgr7dqsTH/JJxLTTsOt7u6keLGt6kNQ=
|
||||||
github.com/sagikazarmark/locafero v0.4.0/go.mod h1:Pe1W6UlPYUk/+wc/6KFhbORCfqzgYEpgQ3O5fPuL3H4=
|
github.com/sagikazarmark/locafero v0.4.0/go.mod h1:Pe1W6UlPYUk/+wc/6KFhbORCfqzgYEpgQ3O5fPuL3H4=
|
||||||
github.com/sagikazarmark/slog-shim v0.1.0 h1:diDBnUNK9N/354PgrxMywXnAwEr1QZcOr6gto+ugjYE=
|
github.com/sagikazarmark/slog-shim v0.1.0 h1:diDBnUNK9N/354PgrxMywXnAwEr1QZcOr6gto+ugjYE=
|
||||||
github.com/sagikazarmark/slog-shim v0.1.0/go.mod h1:SrcSrq8aKtyuqEI1uvTDTK1arOWRIczQRv+GVI1AkeQ=
|
github.com/sagikazarmark/slog-shim v0.1.0/go.mod h1:SrcSrq8aKtyuqEI1uvTDTK1arOWRIczQRv+GVI1AkeQ=
|
||||||
github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529 h1:nn5Wsu0esKSJiIVhscUtVbo7ada43DJhG55ua/hjS5I=
|
|
||||||
github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
|
|
||||||
github.com/sergi/go-diff v1.3.1 h1:xkr+Oxo4BOQKmkn/B9eMK0g5Kg/983T9DqqPHwYqD+8=
|
github.com/sergi/go-diff v1.3.1 h1:xkr+Oxo4BOQKmkn/B9eMK0g5Kg/983T9DqqPHwYqD+8=
|
||||||
github.com/sergi/go-diff v1.3.1/go.mod h1:aMJSSKb2lpPvRNec0+w3fl7LP9IOFzdc9Pa4NFbPK1I=
|
github.com/sergi/go-diff v1.3.1/go.mod h1:aMJSSKb2lpPvRNec0+w3fl7LP9IOFzdc9Pa4NFbPK1I=
|
||||||
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
|
|
||||||
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
|
|
||||||
github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo=
|
github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo=
|
||||||
github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0=
|
github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0=
|
||||||
github.com/spf13/afero v1.11.0 h1:WJQKhtpdm3v2IzqG8VMqrr6Rf3UYpEF239Jy9wNepM8=
|
github.com/spf13/afero v1.11.0 h1:WJQKhtpdm3v2IzqG8VMqrr6Rf3UYpEF239Jy9wNepM8=
|
||||||
github.com/spf13/afero v1.11.0/go.mod h1:GH9Y3pIexgf1MTIWtNGyogA5MwRIDXGUr+hbWNoBjkY=
|
github.com/spf13/afero v1.11.0/go.mod h1:GH9Y3pIexgf1MTIWtNGyogA5MwRIDXGUr+hbWNoBjkY=
|
||||||
github.com/spf13/cast v1.6.0 h1:GEiTHELF+vaR5dhz3VqZfFSzZjYbgeKDpBxQVS4GYJ0=
|
github.com/spf13/cast v1.6.0 h1:GEiTHELF+vaR5dhz3VqZfFSzZjYbgeKDpBxQVS4GYJ0=
|
||||||
github.com/spf13/cast v1.6.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo=
|
github.com/spf13/cast v1.6.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo=
|
||||||
github.com/spf13/cobra v1.7.0 h1:hyqWnYt1ZQShIddO5kBpj3vu05/++x6tJ6dg8EC572I=
|
|
||||||
github.com/spf13/cobra v1.7.0/go.mod h1:uLxZILRyS/50WlhOIKD7W6V5bgeIt+4sICxh6uRMrb0=
|
|
||||||
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
|
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
|
||||||
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||||
github.com/spf13/viper v1.18.0 h1:pN6W1ub/G4OfnM+NR9p7xP9R6TltLUzp5JG9yZD3Qg0=
|
github.com/spf13/viper v1.18.0 h1:pN6W1ub/G4OfnM+NR9p7xP9R6TltLUzp5JG9yZD3Qg0=
|
||||||
github.com/spf13/viper v1.18.0/go.mod h1:EKmWIqdnk5lOcmR72yw6hS+8OPYcwD0jteitLMVB+yk=
|
github.com/spf13/viper v1.18.0/go.mod h1:EKmWIqdnk5lOcmR72yw6hS+8OPYcwD0jteitLMVB+yk=
|
||||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
|
||||||
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||||
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
||||||
github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY=
|
|
||||||
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
|
|
||||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
|
||||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
|
||||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals=
|
|
||||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||||
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||||
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
|
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
|
||||||
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
|
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
|
||||||
github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8=
|
github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8=
|
||||||
github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU=
|
github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU=
|
||||||
github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM=
|
|
||||||
github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
|
github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
|
||||||
github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
|
github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
|
||||||
github.com/ugorji/go/codec v1.3.0 h1:Qd2W2sQawAfG8XSvzwhBeoGq71zXOC/Q1E9y/wUcsUA=
|
github.com/ugorji/go/codec v1.3.0 h1:Qd2W2sQawAfG8XSvzwhBeoGq71zXOC/Q1E9y/wUcsUA=
|
||||||
@@ -343,67 +189,19 @@ go.yaml.in/yaml/v2 v2.4.2 h1:DzmwEr2rDGHl7lsFgAHxmNz/1NlQ7xLIrlN2h5d1eGI=
|
|||||||
go.yaml.in/yaml/v2 v2.4.2/go.mod h1:081UH+NErpNdqlCXm3TtEran0rJZGxAYx9hb/ELlsPU=
|
go.yaml.in/yaml/v2 v2.4.2/go.mod h1:081UH+NErpNdqlCXm3TtEran0rJZGxAYx9hb/ELlsPU=
|
||||||
golang.org/x/arch v0.20.0 h1:dx1zTU0MAE98U+TQ8BLl7XsJbgze2WnNKF/8tGp/Q6c=
|
golang.org/x/arch v0.20.0 h1:dx1zTU0MAE98U+TQ8BLl7XsJbgze2WnNKF/8tGp/Q6c=
|
||||||
golang.org/x/arch v0.20.0/go.mod h1:bdwinDaKcfZUGpH09BB7ZmOfhalA8lQdzl62l8gGWsk=
|
golang.org/x/arch v0.20.0/go.mod h1:bdwinDaKcfZUGpH09BB7ZmOfhalA8lQdzl62l8gGWsk=
|
||||||
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
golang.org/x/crypto v0.41.0 h1:WKYxWedPGCTVVl5+WHSSrOBT0O8lx32+zxmHxijgXp4=
|
||||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
golang.org/x/crypto v0.41.0/go.mod h1:pO5AFd7FA68rFak7rOAGVuygIISepHftHnr8dr6+sUc=
|
||||||
golang.org/x/crypto v0.0.0-20190923035154-9ee001bba392/go.mod h1:/lpIB1dKB+9EgE3H3cr1v9wB50oz8l4C4h62xy7jSTY=
|
golang.org/x/exp v0.0.0-20230905200255-921286631fa9 h1:GoHiUyI/Tp2nVkLI2mCxVkOjsbSXD66ic0XW0js0R9g=
|
||||||
golang.org/x/crypto v0.43.0 h1:dduJYIi3A3KOfdGOHX8AVZ/jGiyPa3IbBozJ5kNuE04=
|
golang.org/x/exp v0.0.0-20230905200255-921286631fa9/go.mod h1:S2oDrQGGwySpoQPVqRShND87VCbxmc6bL1Yd2oYrm6k=
|
||||||
golang.org/x/crypto v0.43.0/go.mod h1:BFbav4mRNlXJL4wNeejLpWxB7wMbc79PdRGhWKncxR0=
|
golang.org/x/mod v0.26.0 h1:EGMPT//Ezu+ylkCijjPc+f4Aih7sZvaAr+O3EHBxvZg=
|
||||||
golang.org/x/exp v0.0.0-20250808145144-a408d31f581a h1:Y+7uR/b1Mw2iSXZ3G//1haIiSElDQZ8KWh0h+sZPG90=
|
golang.org/x/mod v0.26.0/go.mod h1:/j6NAhSk8iQ723BGAUyoAcn7SlD7s15Dp9Nd/SfeaFQ=
|
||||||
golang.org/x/exp v0.0.0-20250808145144-a408d31f581a/go.mod h1:rT6SFzZ7oxADUDx58pcaKFTcZ+inxAa9fTrYx/uVYwg=
|
golang.org/x/net v0.43.0 h1:lat02VYK2j4aLzMzecihNvTlJNQUq316m2Mr9rnM6YE=
|
||||||
golang.org/x/mod v0.28.0 h1:gQBtGhjxykdjY9YhZpSlZIsbnaE2+PgjfLWUQTnoZ1U=
|
golang.org/x/net v0.43.0/go.mod h1:vhO1fvI4dGsIjh73sWfUVjj3N7CA9WkKJNQm2svM6Jg=
|
||||||
golang.org/x/mod v0.28.0/go.mod h1:yfB/L0NOf/kmEbXjzCPOx1iK1fRutOydrCMsqRhEBxI=
|
|
||||||
golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
|
||||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
|
||||||
golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
|
||||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
|
||||||
golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
|
||||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
|
||||||
golang.org/x/net v0.0.0-20210410081132-afb366fc7cd1/go.mod h1:9tjilg8BloeKEkVJvy7fQ90B1CfIiPueXVOjqfkSzI8=
|
|
||||||
golang.org/x/net v0.45.0 h1:RLBg5JKixCy82FtLJpeNlVM0nrSqpCRYzVU1n8kj0tM=
|
|
||||||
golang.org/x/net v0.45.0/go.mod h1:ECOoLqd5U3Lhyeyo/QDCEVQ4sNgYsqvCZ722XogGieY=
|
|
||||||
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
|
||||||
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
|
||||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
|
||||||
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
|
||||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
|
||||||
golang.org/x/sync v0.17.0 h1:l60nONMj9l5drqw6jlhIELNv9I0A4OFgRsG9k2oT9Ug=
|
|
||||||
golang.org/x/sync v0.17.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
|
|
||||||
golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
|
||||||
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
|
||||||
golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
|
||||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
|
||||||
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
|
||||||
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20190922100055-0a153f010e69/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20210303074136-134d130e1a04/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
|
||||||
golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
|
||||||
golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
|
||||||
golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
|
||||||
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
|
||||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.37.0 h1:fdNQudmxPjkdUTPnLn5mdQv7Zwvbvpaxqs831goi9kQ=
|
golang.org/x/sys v0.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI=
|
||||||
golang.org/x/sys v0.37.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
||||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
golang.org/x/text v0.28.0 h1:rhazDwis8INMIwQ4tpjLDzUhx6RlXqZNPEM0huQojng=
|
||||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
golang.org/x/text v0.28.0/go.mod h1:U8nCwOR8jO/marOQ0QbDiOngZVEBB7MAiitBuMjXiNU=
|
||||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
|
||||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
|
||||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
|
||||||
golang.org/x/text v0.30.0 h1:yznKA/E9zq54KzlzBEAWn1NXSQ8DIp/NYMy88xJjl4k=
|
|
||||||
golang.org/x/text v0.30.0/go.mod h1:yDdHFIX9t+tORqspjENWgzaCVXgk0yYnYuSZ8UzzBVM=
|
|
||||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
|
||||||
golang.org/x/tools v0.0.0-20190907020128-2ca718005c18/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
|
||||||
golang.org/x/tools v0.37.0 h1:DVSRzp7FwePZW356yEAChSdNcQo6Nsp+fex1SUW09lE=
|
|
||||||
golang.org/x/tools v0.37.0/go.mod h1:MBN5QPQtLMHVdvsbtarmTNukZDdgwdwlO5qGacAzF0w=
|
|
||||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
|
||||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
|
||||||
gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk=
|
gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk=
|
||||||
gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E=
|
gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E=
|
||||||
google.golang.org/genproto/googleapis/api v0.0.0-20250825161204-c5933d9347a5 h1:BIRfGDEjiHRrk0QKZe3Xv2ieMhtgRGeLcZQ0mIVn4EY=
|
google.golang.org/genproto/googleapis/api v0.0.0-20250825161204-c5933d9347a5 h1:BIRfGDEjiHRrk0QKZe3Xv2ieMhtgRGeLcZQ0mIVn4EY=
|
||||||
@@ -414,17 +212,11 @@ google.golang.org/grpc v1.75.0 h1:+TW+dqTd2Biwe6KKfhE5JpiYIBWq865PhKGSXiivqt4=
|
|||||||
google.golang.org/grpc v1.75.0/go.mod h1:JtPAzKiq4v1xcAB2hydNlWI2RnF85XXcV0mhKXr2ecQ=
|
google.golang.org/grpc v1.75.0/go.mod h1:JtPAzKiq4v1xcAB2hydNlWI2RnF85XXcV0mhKXr2ecQ=
|
||||||
google.golang.org/protobuf v1.36.8 h1:xHScyCOEuuwZEc6UtSOvPbAT4zRh0xcNRYekJwfqyMc=
|
google.golang.org/protobuf v1.36.8 h1:xHScyCOEuuwZEc6UtSOvPbAT4zRh0xcNRYekJwfqyMc=
|
||||||
google.golang.org/protobuf v1.36.8/go.mod h1:fuxRtAxBytpl4zzqUh6/eyUujkJdNiuEkXntxiD/uRU=
|
google.golang.org/protobuf v1.36.8/go.mod h1:fuxRtAxBytpl4zzqUh6/eyUujkJdNiuEkXntxiD/uRU=
|
||||||
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
|
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
|
||||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
||||||
gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA=
|
gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA=
|
||||||
gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
|
gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
|
||||||
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
|
||||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
|
||||||
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
|
||||||
gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
|
||||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
|
|||||||
@@ -1,51 +0,0 @@
|
|||||||
// Package client provides service client factory for creating service clients.
|
|
||||||
package client
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"git.dcentral.systems/toolz/goplt/internal/client/grpc"
|
|
||||||
"git.dcentral.systems/toolz/goplt/pkg/registry"
|
|
||||||
"git.dcentral.systems/toolz/goplt/pkg/services"
|
|
||||||
)
|
|
||||||
|
|
||||||
// ServiceClientFactory creates service clients for inter-service communication.
|
|
||||||
type ServiceClientFactory struct {
|
|
||||||
registry registry.ServiceRegistry
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewServiceClientFactory creates a new service client factory.
|
|
||||||
func NewServiceClientFactory(reg registry.ServiceRegistry) *ServiceClientFactory {
|
|
||||||
return &ServiceClientFactory{
|
|
||||||
registry: reg,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetAuthClient returns an AuthServiceClient.
|
|
||||||
func (f *ServiceClientFactory) GetAuthClient() (services.AuthServiceClient, error) {
|
|
||||||
return grpc.NewAuthClient(f.registry)
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetIdentityClient returns an IdentityServiceClient.
|
|
||||||
func (f *ServiceClientFactory) GetIdentityClient() (services.IdentityServiceClient, error) {
|
|
||||||
return grpc.NewIdentityClient(f.registry)
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetAuthzClient returns an AuthzServiceClient.
|
|
||||||
func (f *ServiceClientFactory) GetAuthzClient() (services.AuthzServiceClient, error) {
|
|
||||||
return grpc.NewAuthzClient(f.registry)
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetAuditClient returns an AuditServiceClient.
|
|
||||||
func (f *ServiceClientFactory) GetAuditClient() (services.AuditServiceClient, error) {
|
|
||||||
return grpc.NewAuditClient(f.registry)
|
|
||||||
}
|
|
||||||
|
|
||||||
// DiscoverService discovers service instances for a given service name.
|
|
||||||
func (f *ServiceClientFactory) DiscoverService(ctx context.Context, serviceName string) ([]*registry.ServiceInstance, error) {
|
|
||||||
if f.registry == nil {
|
|
||||||
return nil, fmt.Errorf("service registry is not available")
|
|
||||||
}
|
|
||||||
return f.registry.Discover(ctx, serviceName)
|
|
||||||
}
|
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user