---
layout: 'page'
uri: '/framework/domain/errors-events'
position: 3
slug: 'framework-domain-errors-events'
parent: 'framework-domain'
navTitle: 'Errors & Events'
title: 'Errors & Events'
description: 'Doménové error typy (ValidationError, AuthError, PermissionError) a domain events s EventCollector.'
---

# Errors & Events


## Proč

Doménové errory nesou sémantiku chyby (validace, oprávnění) bez závislosti na HTTP. Domain events umožňují asynchronní side-effects (notifikace, audit) po úspěšném commandu -- pokud command selže, eventy se zahodí.


## Jak

### ValidationError

Vstupní validace a business pravidla. Žije v `domain/shared/errors.go`.

```go
package shared

type ValidationError struct {
    Field   string
    Message string
}

func (e *ValidationError) Error() string      { return e.Message }
func (e *ValidationError) HTTPStatus() int    { return 400 }
func (e *ValidationError) ErrorField() string { return e.Field }
```

Metoda `ErrorField()` implementuje `response.FieldError` -- response writer díky tomu pošle chybu pod klíčem podle pole (`{"nickname": "..."}`), takže frontend ji namapuje rovnou na příslušný input. Když je `Field` prázdný, chyba spadne do `general` klíče. Detaily v [Forms guide](/guides/forms).

Použití ve value objects:

```go
nickname, err := user.NewNickname("")
// err = &shared.ValidationError{Field: "nickname", Message: "nickname is required"}
```


### AuthError

Nejsi autentizován (chybějící / neplatné / expirované credentials). Mapuje na HTTP 401.

```go
type AuthError struct {
    Message string
}

func (e *AuthError) Error() string   { return e.Message }
func (e *AuthError) HTTPStatus() int { return 401 }
```

Použití: JWT middleware (neplatný Bearer), command handlery (neznámý login, expirovaný refresh token, missing claims).


### PermissionError

Jsi autentizován, ale nemáš právo na danou operaci. Mapuje na HTTP 403.

```go
type PermissionError struct {
    Message string
}

func (e *PermissionError) Error() string   { return e.Message }
func (e *PermissionError) HTTPStatus() int { return 403 }
```

Použití: `PermissionChecker` v bus `AuthorizeMiddleware` (role nemá požadovanou permission). HTTP role guard middleware není potřeba -- bus middleware to vynucuje sám pro každý command/query.


### DomainEvent interface

Žije v `domain/shared/event.go`. Eventy jsou čisté data structs s primitivy (serializovatelné).

```go
package shared

type DomainEvent interface {
    EventName() string
    OccurredAt() time.Time
}
```


### Příklad eventu

Žije v `domain/user/user_created.go`:

```go
package user

type UserCreated struct {
    UserID    string
    Nickname  string
    Email     string
    Role      string
    Timestamp time.Time
}

func (e UserCreated) EventName() string     { return "user.created" }
func (e UserCreated) OccurredAt() time.Time { return e.Timestamp }
```


### EventCollector

Sbírá eventy v rámci jednoho command dispatch. Žije v `domain/shared/event.go`. Per-request instanci vytváří `DispatchEventsMiddleware` a ukládá ji do `ctx`; handlery ji čtou helperem `EventCollectorFromContext(ctx)`.

```go
type EventCollector struct { /* mutex + slice */ }

func NewEventCollector() *EventCollector
func (c *EventCollector) Collect(event DomainEvent)
func (c *EventCollector) Flush() []DomainEvent

func ContextWithEventCollector(ctx context.Context) (context.Context, *EventCollector)
func EventCollectorFromContext(ctx context.Context) *EventCollector
```

Použití v command handleru:

```go
func (h *CreateUserHandler) Handle(ctx context.Context, cmd CreateUserCommand) error {
    // ... vytvoření uživatele ...
    shared.EventCollectorFromContext(ctx).Collect(user.UserCreated{
        UserID:    u.ID,
        Nickname:  u.Nickname,
        Email:     u.Email,
        Role:      u.Role,
        Timestamp: time.Now(),
    })
    return nil
}
```

Pokud handler běží mimo bus (např. CLI `create-user`, který bus bypassuje), `EventCollectorFromContext` vrátí throwaway collector — `Collect` projde, ale eventy nikam nejdou.


### Životní cyklus eventů

```
1. DispatchEventsMiddleware vytvoří per-request EventCollector v ctx
2. TransactionMiddleware otevře transakci
3. Command handler volá EventCollectorFromContext(ctx).Collect(event)
4. TransactionMiddleware commitne (nebo rollbackne při chybě)
5. DispatchEventsMiddleware: pokud commit OK, flushne eventy -> EventBus.Dispatch (synchronně)
6. Event handler zpracuje side-effect (email, notifikace)

Pokud command nebo commit selže, chyba propaguje zpět skrz DispatchEvents -> flush se přeskočí, eventy se zahodí.
```


## Detaily

### Error -> HTTP mapování

Doménové errory implementují `HTTPStatus() int` metodu implicitně (Go duck typing). Presentation vrstva (`response/` balíček) definuje vlastní `HTTPError` interface a mapuje errory na HTTP status kódy -- domain nepotřebuje importovat response.

| Error | Status | Kdy |
|---|---|---|
| `*shared.ValidationError` | 400 | Value object validace, business pravidla |
| `*shared.AuthError` | 401 | Chybí/neplatné/expirované credentials, missing claims |
| `*shared.PermissionError` | 403 | Autentizován, ale nemá právo (role neodpovídá) |
| Ostatní | 500 | Systémové chyby |

### Event konvence

- Eventy používají jen primitivy (`string`, `time.Time`), ne celé entity ani value objects.
- Jmenování: `<Entity><Action>` (např. `UserCreated`, `TokenRevoked`).
- `EventName()` vrací `<kontext>.<akce>` (např. `"user.created"`).
- Event handler nesmí modifikovat stav v rámci původní transakce -- běží asynchronně po commitu.
- Pro cross-domain komunikaci: kontext B reaguje na event z kontextu A bez importu A (používá jen primitivní data z eventu).

---

[← Interfaces](/framework/domain/interfaces.md) | [Application →](/framework/application.md)