> ## Documentation Index
> Fetch the complete documentation index at: https://docs.diyanet.pro/llms.txt
> Use this file to discover all available pages before exploring further.

# Value Object'ler

> Değişmezlik, constructor validasyonu ve hazır value object / enumeration kataloğu.

Value object (VO), **kimliği olmayan**, yalnızca taşıdığı değere göre eşitlenen ve oluşturulduktan sonra **değişmeyen** (immutable) bir nesnedir. İki `Email("a@b.com")` örneği eşittir; çünkü onları ayıran bir kimlik yoktur — yalnızca değerleri vardır.

VO'lar domain modelinde iki kritik iş görür:

1. **Geçersiz durumu temsil edilemez kılar.** `Email` nesnesi varsa, o e-posta zaten regex'ten geçmiştir. Aggregate içinde tekrar doğrulamaya gerek yoktur.
2. **Anlam taşır.** `string phone` yerine `Phone`; ham veri değil, kuralları kendi içinde taşıyan bir kavram.

## Tasarım: değişmezlik + ctor validasyonu

Tüm VO'lar `ValueObject` tabanından türer ve şu deseni izler:

* Doğrulama **constructor'da** (veya statik fabrika metodunda) yapılır; ihlalde `DomainException` fırlatılır.
* Alanlar `get`-only'dir; nesne oluştuktan sonra değişmez.
* `GetAtomicValues()` eşitliğe katkıda bulunan alanları sıralar.
* EF Core materializasyonu için `protected` parametresiz ctor bulunur.

```csharp theme={null}
public sealed class FullName : ValueObject
{
    public string Value { get; }

    protected FullName() {}                 // EF Core için
    public FullName(string value)
    {
        if (string.IsNullOrWhiteSpace(value))
            throw new DomainException("Ad Soyad boş olamaz.");

        var normalized = Normalize(value);  // whitespace + Unicode FormC
        if (normalized.Length < 2 || normalized.Length > 200)
            throw new DomainException("Ad Soyad uzunluğu geçersiz.");
        if (!IsValid(normalized))           // Unicode harf + boşluk + . - '
            throw new DomainException("Ad Soyad geçersiz karakterler içeriyor.");

        Value = normalized;
    }

    protected override IEnumerable<object> GetAtomicValues() { yield return Value; }
}
```

<Tip>
  `FullName` bilinçli olarak adı ad/soyad diye **bölmez** — kültürler arası isim yapıları (çoklu soyad, mononim, patronimik) farklıdır. Tek bir normalize edilmiş değer tutulur.
</Tip>

Bazı VO'lar `public` ctor yerine **statik fabrika metodu** kullanır (özellikle üretim/hash gibi yan etki içerenler):

```csharp theme={null}
public class OtpCode : ValueObject
{
    public const int MinCode = 1000, MaxCode = 9999;
    public string Value { get; }

    internal OtpCode(string value) => Value = value;

    public static OtpCode Create(string code)
    {
        if (string.IsNullOrWhiteSpace(code) || !int.TryParse(code, out var v) || v < MinCode || v > MaxCode)
            throw new DomainException("Hatalı OTP kodu");
        return new OtpCode(code);
    }

    public static OtpCode Generate()
        => new OtpCode(new Random().Next(MinCode, MaxCode + 1).ToString());
}
```

## VO kataloğu

`SharedKernel/ValueObjects/` altındaki başlıca value object'ler:

| VO                      | Kural / Davranış                                                                                         |
| ----------------------- | -------------------------------------------------------------------------------------------------------- |
| `FullName`              | 2–200 karakter, Unicode harf + `. - '`, whitespace ve FormC normalizasyonu.                              |
| `Email`                 | RFC benzeri regex doğrulaması.                                                                           |
| `Phone`                 | `MobilePhoneUtility` ile doğrulama + formatlama. `ToNetGsmFormat()` NetGSM için ham GSM döndürür.        |
| `OtpCode`               | 4 hane (1000–9999). `Create(string)` doğrular, `Generate()` rastgele üretir.                             |
| `RefreshTokenInfo`      | Düz token saklanmaz; **SHA256 hash**'i tutulur. `Create(plain, expiresAt)`, `Matches(plain)`.            |
| `TotpConfigurationInfo` | TOTP yapılandırması (immutable). `Configure/Enable/Disable/MarkUsed`; `CanAccept(step)` replay koruması. |
| `TotpSecret`            | Şifrelenmiş TOTP sırrı. `Create(encrypted)`.                                                             |
| `TCKimlikNo`            | 11 hane + checksum algoritması. `IsForeign()` (99/98/97 ile başlayan).                                   |
| `HES`                   | HES kodu format doğrulaması.                                                                             |
| `Age`                   | Doğum tarihinden yaş/gün/ay/yıl hesaplar (`IFormattable`).                                               |
| `GeoLocation`           | Enlem (-90..90) / boylam (-180..180) doğrulaması.                                                        |
| `Image`                 | **ImageMagick (Magick.NET)** ile resize + watermark; `byte[]` saklar.                                    |
| `File`                  | Dosya içeriği + metadata VO'su.                                                                          |

### Telefon — NetGSM formatı

```csharp theme={null}
public class Phone : ValueObject
{
    public string Value { get; protected set; }
    public Phone(string number)
    {
        if (!MobilePhoneUtility.Instance.IsValidNumber(number))
            throw new DomainException("Geçersiz telefon numarası");
        Value = MobilePhoneUtility.Instance.Format(number);
    }

    public string ToNetGsmFormat()  // +90 542 123 45 67 -> 5421234567
    {
        var digits = new string(Value.Where(char.IsDigit).ToArray());
        return digits.StartsWith("90") ? digits.Substring(2) : digits;
    }
}
```

### Refresh token — düz değer asla saklanmaz

```csharp theme={null}
public sealed class RefreshTokenInfo : ValueObject
{
    public string TokenHash { get; private set; }
    public DateTime ExpiresAt { get; private set; }
    public bool IsExpired => DateTime.UtcNow >= ExpiresAt;

    public static RefreshTokenInfo Create(string plainToken, DateTime expiresAt)
        => new(Hash(plainToken), expiresAt);   // SHA256

    public bool Matches(string plainToken) => TokenHash == Hash(plainToken);
}
```

<Warning>
  `RefreshTokenInfo` düz token'ı **hiçbir zaman** saklamaz; yalnızca SHA256 hash'ini tutar. Doğrulama `Matches(plain)` ile hash karşılaştırması üzerinden yapılır. DB sızıntısında token'lar açığa çıkmaz.
</Warning>

## EF Core ile eşleme — kısaca

Value object'ler ayrı tablo değil, sahibi olan entity'nin **kolonlarına** açılır. EF Core'da bu, `HasConversion` (tek değerli VO için) veya `ComplexProperty`/`OwnsOne` (çok alanlı VO için) ile yapılır:

```csharp theme={null}
// Tek değerli VO → tek kolon
builder.Property(u => u.Email)
       .HasConversion(e => e.Value, v => new Email(v));
```

<Info>
  Eşleme detayları (hangi VO `HasConversion`, hangisi owned type olarak çıkarılıyor) Veri Katmanı dokümanındadır. Domain tarafı persistence'tan habersizdir — VO'lar EF Core'a göre değil, iş kuralına göre tasarlanır.
</Info>

## Enumeration örnekleri

Sabit kümeler `enum` yerine `Enumeration` ile modellenir (bkz. [SharedKernel](/domain/shared-kernel)). Kimlik doğrulama ve yetkilendirmede sık kullanılanlar:

```csharp theme={null}
public class OtpType : Enumeration
{
    public static OtpType Sms           = new(1, nameof(Sms));
    public static OtpType Email         = new(2, nameof(Email));
    public static readonly OtpType Authenticator = new(3, nameof(Authenticator));
}

public class AuthProviderType : Enumeration
{
    public static AuthProviderType Google   = new(1, nameof(Google));
    public static AuthProviderType Meta     = new(2, nameof(Meta));
    public static AuthProviderType Apple    = new(3, nameof(Apple));
    public static AuthProviderType Keycloak = new(4, nameof(Keycloak));
}

public class Role : Enumeration
{
    public static Role SuperAdmin = new(1, nameof(SuperAdmin));
    public static Role Admin      = new(2, nameof(Admin));
    public static Role Staff      = new(3, nameof(Staff));
    public static Role ReadOnly   = new(4, nameof(ReadOnly));
}
```

| Enumeration        | Değerler                                                      |
| ------------------ | ------------------------------------------------------------- |
| `OtpType`          | `Sms=1`, `Email=2`, `Authenticator=3`                         |
| `AuthProviderType` | `Google=1`, `Meta=2`, `Apple=3`, `Keycloak=4`                 |
| `Role`             | `SuperAdmin=1`, `Admin=2`, `Staff=3`, `ReadOnly=4`            |
| `UserStatus`       | `Draft=0`, `Active=1`, `Suspended=2`, `Banned=3`, `Pending=4` |

## Sonraki adımlar

<CardGroup cols={2}>
  <Card title="Aggregate'ler" icon="cubes" href="/domain/aggregates">
    VO'ların `User` ve diğer aggregate'lerde kullanımı.
  </Card>

  <Card title="SharedKernel" icon="layer-group" href="/domain/shared-kernel">
    `ValueObject` ve `Enumeration` taban sınıfları.
  </Card>

  <Card title="Veri Katmanı" icon="database" href="/data/dbcontext">
    VO → kolon eşlemesi ve `HasConversion` detayları.
  </Card>

  <Card title="Domain Event'ler" icon="bolt" href="/domain/domain-events">
    VO'ları payload olarak taşıyan event'ler.
  </Card>
</CardGroup>
