# DSLリファレンス

Complete reference for Goa's design language - data modeling, services, methods, HTTP/gRPC mapping, and security.

Source: https://goa.design/ja/docs/1-goa/dsl-reference/

Relative links resolve against the source URL above.


Goaのドメイン固有言語(DSL)は、デザインファーストアプローチの基礎となるものです。このリファレンスは、基本的な型定義から複雑なトランスポートマッピングやセキュリティスキームまで、DSLのあらゆる側面をカバーしています。

## データモデリング

Goaは、あなたのドメインを正確にモデル化するための強力な型システムを提供します。単純なプリミティブから複雑な入れ子構造に至るまで、DSLはデータ関係、 制約、検証ルールを表現する自然な方法を提供します。

### プリミティブ型

Goa には以下の組み込みプリミティブ型があります：

```go
Boolean  // JSON boolean
Int      // Signed integer
Int32    // Signed 32-bit integer 
Int64    // Signed 64-bit integer
UInt     // Unsigned integer
UInt32   // Unsigned 32-bit integer
UInt64   // Unsigned 64-bit integer
Float32  // 32-bit floating number
Float64  // 64-bit floating number
String   // JSON string
Bytes    // Binary data
Any      // Arbitrary JSON value
```

### 型の定義

`Type` DSL関数は、構造化データ型を定義する主な方法です：

```go
var Person = Type("Person", func() {
    Description("A person")
    
    // Basic attribute
    Attribute("name", String)
    
    // Attribute with validation
    Attribute("age", Int32, func() {
        Minimum(0)
        Maximum(120)
    })
    
    // Required fields
    Required("name", "age")
})
```

### 複合型

#### 配列

配列は、任意の検証を伴う順序付きコレクションを定義します：

```go
var Names = ArrayOf(String, func() {
    MinLength(1)
    MaxLength(10)
})

var Team = Type("Team", func() {
    Attribute("members", ArrayOf(Person))
})
```

すべての item が存在する必要がある場合は `ArrayOfRequired` を使います。primitive、
primitive alias、object のいずれにも適用されます。受信 JSON／JSON-RPC array の
`null` は拒否されますが、`""`、`0`、`false` は有効な item です。

```go
var Alias = Type("Alias", String)

var Names = ArrayOfRequired(Alias, func() {
    MinLength(1)
})
```

#### OneOf

`OneOf` は、必ず一つだけ branch を選ぶ選択肢を宣言します。caller が branch を
選ばなければならない場合は、その `OneOf` attribute を required にします。

```go
var Lifecycle = Type("Lifecycle", func() {
    OneOf("state", func() {
        Attribute("active", Active)
        Attribute("inactive", Empty)
    })
    Required("state")
})
```

required `OneOf` は、branch 未選択、value が nil の typed wrapper、nil の message、
byte slice、`Any` を拒否します。nil ではない empty message は有効です。生成された
constructor、setter、accessor、`Kind` method は branch storage を非公開にし、選択を
一つに保ちます。

#### マップ

マップは、型安全性を備えたキーと値の関連付けを提供します：

```go
var Config = MapOf(String, Int32, func() {
    Key(func() {
        Pattern("^[a-z]+$")
    })
    Elem(func() {
        Minimum(0)
    })
})
```

### 型の構成

#### リファレンス

他の型の属性定義を継承するには `Reference` を使用します：

```go
var Employee = Type("Employee", func() {
    Reference(Person)
    Attribute("name")  // Inherits from Person
    Attribute("age")   // Inherits from Person
    
    Attribute("employeeID", String, func() {
        Format(FormatUUID)
    })
})
```

#### 拡張

`Extend` は、すべての属性を自動的に継承する新しい型を作成します：

```go
var Manager = Type("Manager", func() {
    Extend(Employee)
    Attribute("reports", ArrayOf(Employee))
})
```

### バリデーション・ルール

#### 文字列の検証
- `Pattern(regex)` - 正規表現に対するバリデーション
- `MinLength(n)` - 文字列の長さの最小値
- `MaxLength(n)` - 文字列の最大長
- `Format(format)` - 定義済みのフォーマットに対して検証を行う

#### 数値の検証
- `Minimum(n)` - 最小値(包含)
- `Maximum(n)` - 最大値(数値を含む)
- `ExclusiveMinimum(n)` - 最小値（排他的）。
- `ExclusiveMaximum(n)` - 最大値（排他的）。

#### コレクション検証
- `MinLength(n)` - 最小要素数
- `MaxLength(n)` - 要素の最大数

#### オブジェクトの検証
- `Required("field1", "field2")` - 必須項目

#### 汎用バリデーション
- `Enum(value1, value2)` - 列挙された値に制限する

組み合わせた例

```go
var UserProfile = Type("UserProfile", func() {
    Attribute("username", String, func() {
        Pattern("^[a-z0-9]+$")
        MinLength(3)
        MaxLength(50)
    })
    
    Attribute("email", String, func() {
        Format(FormatEmail)
    })
    
    Attribute("age", Int32, func() {
        Minimum(18)
        ExclusiveMaximum(150)
    })
    
    Attribute("tags", ArrayOf(String, func() { 
        Enum("tag1", "tag2", "tag3") 
    }), func() {
        MinLength(1)
        MaxLength(10)
    })

    Required("username", "email", "age")
})
```

### 組み込みフォーマット

Goaには、一般的なデータパターン用の定義済みフォーマットが含まれています：

| フォーマット
|--------|-------------|
| `FormatDate` | RFC3339 日付の値
| `FormatDateTime` | RFC3339 日付時刻値
| `FormatUUID` | RFC4122 UUIDの値
| `FormatEmail` | RFC5322メールアドレス
| `FormatHostname` | RFC1035 インターネット・ホスト名 | | `FormatIPv4` | RFC4122 UUID値
| `FormatIPv4` | RFC2373 IPv4アドレス値 | | `FormatIPv6` | RFC2373 IPv4アドレス値
| `FormatIPv6` | RFC2373 IPv6アドレスの値
| `FormatIP` | RFC2373のIPv4またはIPv6アドレス値
`FormatURI` | RFC3986 URIの値
`FormatMAC` | IEEE 802 MAC-48/EUI-48/EUI-64アドレス | `FormatCIDR` | IEEE 802 MAC-48/EUI-48/EUI-64アドレスの値
| `FormatCIDR` | RFC4632/RFC4291 CIDR 表記法 | | `FormatRegexp` | RFC3986 URI 値
| `FormatRegexp` | RE2 正規表現構文 | | `FormatJSON` | RE2 正規表現構文
| `FormatJSON` | JSONテキスト
| `FormatRFC1123` | RFC1123 日付時刻値

### 属性とフィールドのDSL

HTTP専用の型には`Attribute`を使う。gRPCサポートが必要な場合は`Field`を使用してください（フィールド番号タグを含む）：

```go
// HTTP only
var Person = Type("Person", func() {
    Attribute("name", String)
    Attribute("age", Int32)
})

// With gRPC support
var Person = Type("Person", func() {
    Field(1, "name", String)
    Field(2, "age", Int32)
})
``` を使う。

### 例

ドキュメント用にサンプル値を提供します：

```go
var User = Type("User", func() {
    Attribute("name", String, func() {
        Example("John Doe")
    })
    
    Attribute("email", String, func() {
        Example("work", "john@work.com")
        Example("personal", "john@gmail.com")
        Format(FormatEmail)
    })
})

var Address = Type("Address", func() {
    Attribute("street", String)
    Attribute("city", String)
    Required("street", "city")
    
    Example("Home Address", func() {
        Description("Example of a residential address")
        Value(Val{
            "street": "123 Main St",
            "city": "Boston",
        })
    })
})
```

例を省略した場合、Goa はデフォルトでランダムなシードを使って例を自動生成します。

決定的な例を生成するには、`Randomizer` 関数を使用して、例が一貫して予測可能になるようにします：

```go
var _ = API("exampleAPI", func() {
    Randomizer(expr.NewDeterministicRandomizer())
})
```

---

## API 定義

`API`関数は、サービスのグローバルプロパティを定義し、デザインのルートとなります。

### 基本構造

```go
var _ = API("calculator", func() {
    Title("Calculator API")
    Description("A simple calculator service")
    Version("1.0.0")
})
```

### 完全な例

```go
var _ = API("bookstore", func() {
    Title("Bookstore API")
    Description("A modern bookstore management API")
    Version("2.0.0")
    TermsOfService("https://example.com/terms")
    
    Contact(func() {
        Name("API Support")
        Email("support@example.com")
        URL("https://example.com/support")
    })
    
    License(func() {
        Name("Apache 2.0")
        URL("https://www.apache.org/licenses/LICENSE-2.0.html")
    })
    
    Docs(func() {
        Description("Comprehensive API documentation")
        URL("https://example.com/docs")
    })
})
``` の場合

### サーバーの構成

APIにアクセスできる場所を定義します：

```go
var _ = API("bookstore", func() {
    Server("production", func() {
        Description("Production server")
        
        Host("production", func() {
            URI("https://{version}.api.example.com")
            URI("grpcs://{version}.grpc.example.com")
            
            Variable("version", String, "API version", func() {
                Default("v2")
                Enum("v1", "v2")
            })
        })
    })
    
    Server("development", func() {
        Host("localhost", func() {
            URI("http://localhost:8000")
            URI("grpc://localhost:8080")
        })
    })
})
```

### APIレベルのエラー

API レベルでは、再利用可能なエラーを一度定義し、そのエラーを返すサービスまたはメソッドで明示的に選択します。API レベルの定義だけで、すべてのサービスがそのエラーを返すようになるわけではありません：

```go
var _ = API("bookstore", func() {
    Error("unauthorized", ErrorResult, "Authentication failed")
    
    HTTP(func() {
        Response("unauthorized", StatusUnauthorized)
    })
    
    GRPC(func() {
        Response("unauthorized", CodeUnauthenticated)
    })
})
``` です。

サービスはこれらのエラーを名前で参照できる：

```go
var _ = Service("billing", func() {
    Error("unauthorized")  // API レベルの定義をこのサービスで選択
})
```

### カスタムエラータイプ

追加の情報が必要なエラーには、カスタムタイプを使用します：

```go
var ValidationError = Type("ValidationError", func() {
    Field(1, "message", String, "Error message")
    Field(2, "field", String, "Field that failed validation")
    Field(3, "value", Any, "Invalid value provided")
    Required("message", "field")
})

Method("create", func() {
    Error("validation_error", ValidationError, "Input validation failed")
})
```

一つの名前付きエラーだけに使う型には、エラー名を判別するフィールドは必要ありません。一つの型を複数の名前付きエラーに使う場合は、`ErrorName("name", String, ...)` を追加し、`name` を必須にして、値を返す前に Goa のエラー名を設定します。

Goa が `Error`、`ErrorName`、`GoaErrorName` メソッドを追加するのは、`Error` に直接渡した型だけです。その中に含まれる名前付き型は通常の型のままです。直接渡した型をペイロードや結果としても使う場合や、その型に `struct:pkg:path` が指定されている場合も、Goa は型を一度だけ生成し、その宣言の隣にエラーメソッドを配置します。`ErrorResult` は Goa 組み込みのサービスエラーであり、設計で定義したカスタムタイプとしては生成されません。

---

## サービスとメソッド

サービスは、特定の機能を提供する関連メソッドをグループ化したものです。

### サービス DSL

```go
var _ = Service("users", func() {
    Description("User management service")
    
    Docs(func() {
        Description("Detailed documentation for the user service")
        URL("https://example.com/docs/users")
    })

    // Service-level errors
    Error("unauthorized", String, "Authentication failed")
    Error("not_found", NotFound, "Resource not found")
    
    // Metadata
    Meta("swagger:tag", "Users")
    
    // Security requirements
    Security(OAuth2, func() {
        Scope("read:users")
        Scope("write:users")
    })
    
    Method("create", func() {
        // ... method definition
    })
    
    Method("list", func() {
        // ... method definition
    })
})
``` サービス・DSL

### メソッド DSL

```go
Method("add", func() {
    Description("Add two numbers together")
    
    Payload(func() {
        Field(1, "a", Int32, "First operand")
        Field(2, "b", Int32, "Second operand")
        Required("a", "b")
    })
    
    Result(Int32)
    
    Error("overflow")
})
``` メソッドDSL

### ペイロードの種類

```go
// Simple payload
Method("getUser", func() {
    Payload(String, "User ID")
    Result(User)
})

// Structured payload
Method("createUser", func() {
    Payload(func() {
        Field(1, "name", String, "User's full name")
        Field(2, "email", String, "Email address", func() {
            Format(FormatEmail)
        })
        Field(3, "role", String, "User role", func() {
            Enum("admin", "user", "guest")
        })
        Required("name", "email", "role")
    })
    Result(User)
})

// Reference to predefined type
Method("updateUser", func() {
    Payload(UpdateUserPayload)
    Result(User)
})
```

### 結果のタイプ

```go
// Simple result
Method("count", func() {
    Result(Int64)
})

// Structured result
Method("search", func() {
    Result(func() {
        Field(1, "items", ArrayOf(User), "Matching users")
        Field(2, "total", Int64, "Total count")
        Required("items", "total")
    })
})
``` 結果の種類

### ストリーミング・メソッド

```go
Method("streamNumbers", func() {
    Description("Stream a sequence of numbers")
    StreamingPayload(Int32)
    StreamingResult(Int32)
})

Method("processEvents", func() {
    StreamingPayload(func() {
        Field(1, "event_type", String)
        Field(2, "data", Any)
        Required("event_type", "data")
    })
    
    Result(func() {
        Field(1, "processed", Int64)
        Field(2, "errors", Int64)
        Required("processed", "errors")
    })
})
```

---

## HTTP トランスポートマッピング

HTTP DSLは、サービスメソッドがどのようにHTTPエンドポイントにマッピング されるかを定義します。

### HTTP リクエストコンポーネント

HTTP リクエストには、ペイロード属性にマッピングできる 4 つの部分があります：

1. **URLパス・パラメーター** - 例えば、`/bottle/{id}` です。
2. **クエリー文字列パラメーター
3. **HTTPヘッダー
4. **リクエストボディ

マッピング式
- `Param` - パスまたはクエリ文字列からロードする。
- `Header` - HTTPヘッダーからロードする。
- `Body` - リクエストボディからロードする

### オブジェクト以外のペイロードのマッピング

プリミティブ、配列、またはマップのペイロードの場合、値は最初に定義された要素からロードされます：

```go
// Path parameter
Method("show", func() {
    Payload(Int)
    HTTP(func() {
        GET("/{id}")
    })
})
// GET /1 → Show(1)

// Array in path (comma-separated)
Method("delete", func() {
    Payload(ArrayOf(String))
    HTTP(func() {
        DELETE("/{ids}")
    })
})
// DELETE /a,b → Delete([]string{"a", "b"})

// Array in query string
Method("list", func() {
    Payload(ArrayOf(String))
    HTTP(func() {
        GET("")
        Param("filter")
    })
})
// GET /?filter=a&filter=b → List([]string{"a", "b"})

// Header
Method("list", func() {
    Payload(Float32)
    HTTP(func() {
        GET("")
        Header("version")
    })
})
// GET / with header version=1.0 → List(1.0)

// Map in body
Method("create", func() {
    Payload(MapOf(String, Int))
    HTTP(func() {
        POST("")
    })
})
// POST / {"a": 1, "b": 2} → Create(map[string]int{"a": 1, "b": 2})
```

### マッピングオブジェクトペイロード

オブジェクトのペイロードでは、各属性がどこから来るのかを指定します：

```go
Method("create", func() {
    Payload(func() {
        Attribute("id", Int)
        Attribute("name", String)
        Attribute("age", Int)
    })
    HTTP(func() {
        POST("/{id}")
        // id from path, name and age from body
    })
})
// POST /1 {"name": "a", "age": 2} → Create(&CreatePayload{ID: 1, Name: "a", Age: 2})
```

オブジェクト以外のボディを指定するには、`Body`を使う：

```go
Method("rate", func() {
    Payload(func() {
        Attribute("id", Int)
        Attribute("rates", MapOf(String, Float64))
    })
    HTTP(func() {
        PUT("/{id}")
        Body("rates")  // Body is the map directly
    })
})
// PUT /1 {"a": 0.5, "b": 1.0} → Rate(&RatePayload{ID: 1, Rates: map[string]float64{...}})
```

### 要素名のマッピング

HTTP 要素名を属性名にマッピングします：

```go
Header("version:X-Api-Version")  // version attribute from X-Api-Version header

Body(func() {
    Attribute("name:n")  // name attribute from "n" field in JSON
    Attribute("age:a")   // age attribute from "a" field in JSON
})
```

---

## gRPCトランスポートマッピング

gRPC DSLは、サービスメソッドがgRPCプロシージャにどのようにマッピングされるかを定義します。

### gRPC の機能

1. **メッセージ・マッピング** - フィールド番号によるリクエスト/レスポンス構造の定義
2. **ステータスコード** - 結果を gRPC ステータスコードにマッピングします。
3. **メタデータ** - gRPCメタデータ処理の設定

### 混合プロトコルのサポート

サービスは HTTP と gRPC の両方をサポートできます：

```go
Method("create", func() {
    Payload(CreatePayload)
    Result(User)
    
    HTTP(func() {
        POST("/")
        Response(StatusCreated)
    })
    
    GRPC(func() {
        Response(CodeOK)
    })
})
``` を参照してください。

RESTfulリソースマッピング：

```go
Service("users", func() {
    HTTP(func() {
        Path("/users")
    })
    
    Method("list", func() {
        HTTP(func() { GET("/") })           // GET /users
        GRPC(func() { Response(CodeOK) })
    })
    
    Method("show", func() {
        HTTP(func() { GET("/{id}") })       // GET /users/{id}
        GRPC(func() { Response(CodeOK) })
    })
})
``` です。

---

## セキュリティ

Goaは認証と認可のためのDSL構造を提供します。

### セキュリティスキーム

#### Bearer トークン

トークン形式をサービス契約の一部にせず、標準の
`Authorization: Bearer <token>` ヘッダーでトークンを受け取る API には
`BearerSecurity` を使用します。これは、不透明なアクセストークン、セッション
トークン、または複数の bearer トークン形式を受け入れる API に適しています。

```go
var BearerAuth = BearerSecurity("bearer", func() {
    Description("Bearer token authentication")
    Scope("api:read", "Read-only access")
    Scope("api:write", "Read and write access")
})
```

#### JWT (JSON ウェブトークン)

トークンが特に JWT であり、生成されるサービス API に JWT 向けの名前を使わせたい
場合は `JWTSecurity` を使用します。HTTP のデフォルトのワイヤ形式は
`BearerSecurity` と同じです。

```go
var JWTAuth = JWTSecurity("jwt", func() {
    Description("JWT-based authentication")
    Scope("api:read", "Read-only access")
    Scope("api:write", "Read and write access")
})
```

OpenAPI v3 ドキュメントで特定の bearer トークン形式を示す必要がある場合にのみ
`BearerFormat` を使用します。`JWTSecurity` はデフォルトで
`bearerFormat: JWT` を出力します。

#### API キー

```go
var APIKeyAuth = APIKeySecurity("api_key", func() {
    Description("API key-based authorization")
})
```

#### 基本認証

```go
var BasicAuth = BasicAuthSecurity("basic", func() {
    Description("Username/password authentication")
    Scope("api:read", "Read-only access")
})
```

#### OAuth2

```go
var OAuth2Auth = OAuth2Security("oauth2", func() {
    AuthorizationCodeFlow(
        "http://auth.example.com/authorize",
        "http://auth.example.com/token",
        "http://auth.example.com/refresh",
    )
    Scope("api:read", "Read-only access")
    Scope("api:write", "Read and write access")
})
```

### セキュリティの適用

#### メソッドレベル

```go
Method("secure_endpoint", func() {
    Security(BearerAuth, func() {
        Scope("api:read")
    })
    
    Payload(func() {
        BearerTokenField(1, "token", String)
        Required("token")
    })
    
    HTTP(func() {
        GET("/secure")
        Header("token:Authorization")
    })
})
```

#### 複数のスキーム

```go
Method("doubly_secure", func() {
    Security(JWTAuth, APIKeyAuth, func() {
        Scope("api:write")
    })
    
    Payload(func() {
        TokenField(1, "token", String)
        APIKeyField(2, "api_key", "key", String)
        Required("token", "key")
    })
    
    HTTP(func() {
        POST("/secure")
        Param("key:k")
    })
})
```

### セキュリティの実装

Goaは、サービスが実装しなければならない`Auther`インターフェースを生成します：

```go
type Auther interface {
    BasicAuth(ctx context.Context, user, pass string, scheme *security.BasicScheme) (context.Context, error)
    BearerAuth(ctx context.Context, token string, scheme *security.BearerScheme) (context.Context, error)
    JWTAuth(ctx context.Context, token string, scheme *security.JWTScheme) (context.Context, error)
    APIKeyAuth(ctx context.Context, key string, scheme *security.APIKeyScheme) (context.Context, error)
    OAuth2Auth(ctx context.Context, token string, scheme *security.OAuth2Scheme) (context.Context, error)
}
```

bearer の実装例:

```go
func (s *svc) BearerAuth(ctx context.Context, token string, scheme *security.BearerScheme) (context.Context, error) {
    scopes, err := s.validateAccessToken(ctx, token)
    if err != nil {
        return ctx, ErrInvalidToken
    }
    if err := scheme.Validate(scopes); err != nil {
        return ctx, ErrInvalidScopes
    }
    return contextWithAuthInfo(ctx, authInfo{token: token}), nil
}
```

JWTの実装例：

```go
func (s *svc) JWTAuth(ctx context.Context, token string, scheme *security.JWTScheme) (context.Context, error) {
    claims := make(jwt.MapClaims)
    
    _, err := jwt.ParseWithClaims(token, claims, func(_ *jwt.Token) (interface{}, error) { 
        return Key, nil 
    })
    if err != nil {
        return ctx, ErrInvalidToken
    }

    // Validate required scopes
    scopes, ok := claims["scopes"].([]any)
    if !ok {
        return ctx, ErrInvalidTokenScopes
    }
    scopesInToken := make([]string, len(scopes))
    for _, scp := range scopes {
        scopesInToken = append(scopesInToken, scp.(string))
    }
    if err := scheme.Validate(scopesInToken); err != nil {
        return ctx, ErrInvalidScopes
    }

    return contextWithAuthInfo(ctx, authInfo{claims: claims}), nil
}
```

Basic Authの実装例：

```go
func (s *svc) BasicAuth(ctx context.Context, user, pass string, scheme *security.BasicScheme) (context.Context, error) {
    if user != "goa" || pass != "rocks" {
        return ctx, ErrUnauthorized
    }
    return contextWithAuthInfo(ctx, authInfo{user: user}), nil
}
```

---

## ベストプラクティス

### タイプ編成
- 関連する型をグループ化する
- 意味のあるフィールド名と説明を使用する
- 一貫した命名規則に従う
- タイプを集中させ、まとまりを持たせる

### バリデーション戦略
- 各フィールドに適切な制約を追加する
- 必須フィールドを明示的に定義する
- 標準的なフォーマットにはフォーマットバリデータを使う
- ドメイン固有のバリデーションルールを考慮する

### サービス設計
- 関連する機能をサービスにまとめる
- サービスの範囲を絞って、まとまりを持たせる
- 明確で説明的なサービス名を使用する
- サービスの目的と使用方法を文書化する

### メソッド設計
- アクション指向の明確なメソッド名を使用する
- 詳細な説明を提供する
- 適切なエラー応答を定義する
- 検証要件を考慮する

### HTTP の設計
- 一貫したURLパターンを使用する
- RESTfulの規約に従う
- 適切なステータスコードを選択する
- 一貫したエラー処理

### セキュリティ
- ユースケースに適したセキュリティスキームを使用する
- 適切なトークン検証を実装する
- 明確なスコープ階層を定義する
- 本番環境で HTTPS を使用する

