---
layout: 'page'
uri: '/framework/domain/interfaces'
position: 2
slug: 'framework-domain-interfaces'
parent: 'framework-domain'
navTitle: 'Interfaces'
title: 'Interfaces'
description: 'Repository a service interfaces -- user.Repository, PasswordHasher, PermissionChecker, JwtService, Transactor, PermissionsRegistry.'
---
# Interfaces
## Proč
Domain definuje _co_ systém umí, ne _jak_. Interfaces žijí v doméně, implementace v infrastructure (`sqlite/user/`, `sqlite/token/`, `security/`). Propojení přes Wire DI. Díky tomu domain nemá žádné závislosti na infrastruktuře.
## Jak
### Repository interfaces
#### user.Repository
Žije v `domain/user/repository.go`:
```go
package user
type Repository interface {
Save(ctx context.Context, user *User) error
Update(ctx context.Context, user *User) error
Delete(ctx context.Context, id string) error
FindByID(ctx context.Context, id string) (*User, error)
FindByNickname(ctx context.Context, nickname string) (*User, error)
FindAllActive(ctx context.Context) ([]User, error)
FindAll(ctx context.Context) ([]User, error)
// Brute-force lock counter — implementace běží na raw poolu (mimo caller tx),
// viz Authentication guide.
RecordFailedLogin(ctx context.Context, userID string, threshold int, window, lockDuration time.Duration) (*time.Time, error)
ResetFailedLogin(ctx context.Context, userID string) error
}
```
Implementace: `infrastructure/sqlite/user/repository.go`
#### token.TokenRepository
Žije v `domain/token/repository.go`:
```go
package token
type TokenRepository interface {
Save(ctx context.Context, token *RefreshToken) error
FindByHash(ctx context.Context, hash string) (*RefreshToken, error)
MarkUsed(ctx context.Context, hash string) (bool, error)
DeleteByUserID(ctx context.Context, userID string) error
DeleteExpired(ctx context.Context) error
}
```
Implementace: `infrastructure/sqlite/token/repository.go`. `MarkUsed` je klíčový pro rotaci refresh tokenů s theft detection — viz [Auth guide](/guides/auth).
### Service interfaces
#### PasswordHasher
Žije v `domain/shared/password.go`:
```go
package shared
type PasswordHasher interface {
Hash(password string) (string, error)
Verify(password, hash string) error
}
```
Implementace: `infrastructure/security/password.go` (`security.PasswordHasher`). Používá SHA-256 prehash před bcrypt -- bcrypt truncuje vstup na 72 bytes, prehash zajistí že celý password je vždy zohledněn.
#### PermissionChecker
Žije v `domain/shared/permission.go`:
```go
package shared
type Permissioned interface {
RequiredPermission() string
}
type SkipPermission interface {
SkipPermissionCheck()
}
type PermissionChecker interface {
Check(ctx context.Context, permission string) error
}
```
Každý command/query MUSÍ implementovat buď `Permissioned`, nebo `SkipPermission`. Bus `AuthorizeMiddleware` to vynucuje -- pokud command neimplementuje ani jeden, vrátí error.
Implementace checkeru: `infrastructure/security/permission.go`. Checker delegate na sdílený helper `shared.IsPermissionAllowedForRole(permission, role)`, který definuje pravidlo "admin má vše, ostatní role nemají `admin:*`". Stejný helper používá i `PermissionsRegistry`.
#### JwtService
Žije v `domain/shared/jwt.go`:
```go
package shared
type JwtService interface {
GenerateAccessToken(claims *AuthClaims) (token string, expiresIn time.Duration, err error)
ValidateAccessToken(token string) (*AuthClaims, error)
GenerateRefreshToken() (raw string, hash string, expiresAt time.Time, err error)
HashRefreshToken(raw string) string
}
```
Implementace: `infrastructure/security/jwt.go` (`*security.JwtService`). Bindingu se děje přes `wire.Bind(new(shared.JwtService), new(*security.JwtService))`. Používá se v `AuthMiddleware`, `LoginHandler`, `RefreshTokenHandler`.
#### PermissionsRegistry
Žije v `domain/shared/permissions_registry.go`:
```go
package shared
type PermissionsRegistry struct { /* interní: sorted, deduplicated list */ }
func NewPermissionsRegistry(items []Permissioned) *PermissionsRegistry
func (r *PermissionsRegistry) All() []string
func (r *PermissionsRegistry) ForRole(role string) []string
```
Sbírá `RequiredPermission()` od všech command/query handlerů implementujících `Permissioned`. Wire provider ji sestavuje v `container_provider.go` — při každém novém Permissioned handleru se přidá instance do slice. `ForRole` filtruje podle stejné logiky jako `PermissionChecker`. HTTP handlery (Login, Profile) ji injektují a plní `user.permissions` v response. Viz [Permissions guide](/guides/permissions).
#### Transactor
Žije v `domain/shared/transactor.go`:
```go
package shared
type Transactor interface {
BeginTx(ctx context.Context) (context.Context, error)
Commit(ctx context.Context) error
Rollback(ctx context.Context) error
}
```
Používá se v bus `TransactionMiddleware`. Implementace: `infrastructure/database/` (SqliteManager).
### AuthClaims context helpers
Žijí v `domain/shared/auth_context.go`. Identita uživatele je doménový koncept -- `AuthClaims` žijí v doméně, ne v security.
```go
package shared
type AuthClaims struct {
UserID string
Role string
Nickname string
}
func ClaimsFromContext(ctx context.Context) *AuthClaims
func ContextWithClaims(ctx context.Context, claims *AuthClaims) context.Context
```
JWT middleware v presentation vrstvě dekóduje token a vloží `AuthClaims` do contextu přes `ContextWithClaims`. Command/query handlery a bus middleware čtou claims přes `ClaimsFromContext`.
Stejný pattern používá `TraceID` (`domain/shared/trace_context.go`):
```go
func TraceIDFromContext(ctx context.Context) string
func ContextWithTraceID(ctx context.Context, traceID string) context.Context
```
## Detaily
- **Repository a I/O porty** (`user.Repository`, `token.TokenRepository`, `AuditLogger`, `Transactor`, `Seeder`, `job.Repository`, `PermissionChecker`) berou `context.Context` jako první parametr -- umožňuje předávat transakci, claims, trace ID. **Čisté výpočetní porty** (`PasswordHasher`, `JwtService`) ho záměrně nemají -- nedělají I/O ani nečtou z contextu.
- Repository interfaces vrací pointery na entity (`*User`, `*RefreshToken`), ne hodnoty.
- `FindByNickname` vrací `nil, nil` když uživatel neexistuje (ne error) -- umožňuje kontrolu existence bez error handlingu.
- `FindByID` vrací `*shared.ValidationError` když uživatel neexistuje -- protože volání s neexistujícím ID je vstupní chyba.
- Seeder v `infrastructure/sqlite/seeder.go` závisí na `user.Repository` (domain interface), ne na konkrétní implementaci.
---
[← Entity & Value Objects](/framework/domain/entities.md) | [Errors & Events →](/framework/domain/errors-events.md)