> ## 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.

# Factory ve Domain Service'ler

> Karmaşık oluşturma için factory, tek aggregate'e sığmayan iş kuralları için domain service. IUserFactory ve UserRegistrationService örnekleri.

Aggregate'in `internal` constructor'ı, oluşturmanın **kontrollü** olmasını sağlar (bkz. [Aggregate'ler](/domain/aggregates)). Ama bir kullanıcı oluşturmadan önce "bu e-posta zaten var mı?" gibi kontroller yapmak gerekir — bu kontroller tek bir aggregate örneğinin sorumluluğu değildir. İşte burada **factory** ve **domain service** devreye girer.

<CardGroup cols={2}>
  <Card title="Factory" icon="industry">
    Karmaşık **oluşturma** mantığını kapsüller: uniqueness kontrolü, dış IdP eşleştirme, doğru ctor seçimi. Sonuç: geçerli bir aggregate.
  </Card>

  <Card title="Domain Service" icon="gears">
    Tek bir aggregate'e doğal olarak ait **olmayan** iş kurallarını barındırır (genelde birden fazla aggregate'i ya da repository'yi ilgilendirir).
  </Card>
</CardGroup>

## IUserFactory — kullanıcı oluşturma fabrikası

`User`'ın ctor'u `internal` olduğu için, dışarıdan oluşturmanın tek meşru yolu factory'dir. Arayüz, tüm kayıt senaryolarını kapsar:

```csharp theme={null}
public interface IUserFactory
{
    Task<User> CreateWithPhoneAsync(FullName? name, Phone? phone, CancellationToken ct);
    Task<User> CreateWithEmailAsync(FullName? name, Email? email, CancellationToken ct);
    Task<User> GetOrCreateFromGoogleAsync(FullName name, Email? email, string googleUserId, CancellationToken ct);
    Task<User> GetOrCreateFromMetaAsync(FullName name, Email? email, string metaUserId, CancellationToken ct);
    Task<User> GetOrCreateFromKeycloakAsync(FullName? name, Email? email, string keycloakUserId, CancellationToken ct);
}
```

İki tür metot vardır:

* **`CreateWith...`** — sıfırdan kayıt (telefon veya e-posta ile). Uniqueness başarısızsa `DomainException` fırlatır.
* **`GetOrCreateFrom...`** — dış IdP (Google/Meta/Keycloak) login akışı. Mevcut kullanıcı varsa bağlar, yoksa oluşturur.

### Uniqueness kontrolü ile oluşturma

```csharp theme={null}
public async Task<User> CreateWithPhoneAsync(FullName? name, Phone? phone, CancellationToken ct)
{
    if (phone == null)
        throw new DomainException("Telefon numarası boş olamaz.");

    var result = await _uniquenessChecker.CheckPhoneUniquenessAsync(phone, ct);

    switch (result)
    {
        case UniquenessResult.Success:
            return new User(name, phone, email: null);        // internal ctor — factory içinden erişilebilir

        case UniquenessResult.AlreadyExists:
            throw new DomainException("Bu telefon numarası ile zaten aktif bir üyelik bulunmaktadır. Lütfen giriş yapınız.");

        case UniquenessResult.ExistsButNotVerified:
            throw new DomainException("Bu telefon numarasına ait bir kayıt mevcut ancak henüz doğrulanmamış. Lütfen sisteme giriş yapıp doğrulama adımını tamamlayın.");

        default:
            throw new ArgumentOutOfRangeException(nameof(result));
    }
}
```

<Note>
  Factory, `User`'ı oluşturmadan **önce** `IUserUniquenessChecker`'a danışır. `internal` ctor sayesinde kimse bu kontrolü atlayarak `User` oluşturamaz — geçersiz durum domain dışına çıkamaz.
</Note>

### Dış IdP akışı — GetOrCreateFromExternalAsync

Google ve Meta ortak bir özel yardımcıyı kullanır. Eşleştirme önceliği üç adımdır:

<Steps>
  <Step title="Provider + sub eşleşmesi">
    `(provider, externalId)` ikilisi daha önce bağlanmışsa, o kullanıcı bulunur ve `linkLogin` ile display name tazelenir.
  </Step>

  <Step title="E-posta eşleşmesi">
    Aynı e-posta başka bir IdP'den gelmiş olabilir. Varsa mevcut kullanıcıya bu provider da eklenir (multi-provider) ve e-posta doğrulanmış sayılır.
  </Step>

  <Step title="Yeni kullanıcı">
    Hiçbir eşleşme yoksa yeni `User` oluşturulur, IdP bağlanır, e-posta varsa `VerifyEmail()` çağrılır.
  </Step>
</Steps>

```csharp theme={null}
private async Task<User> GetOrCreateFromExternalAsync(
    AuthProviderType provider, string externalId, FullName name, Email? email,
    Action<User, string> linkLogin, CancellationToken ct)
{
    // 1) sub eşleşmesi
    var (bySubResult, bySubUser) = await _uniquenessChecker.CheckExternalIdentityUniquenessAsync(provider, externalId, ct);
    if (bySubResult == UniquenessResult.AlreadyExists && bySubUser is not null)
    {
        linkLogin(bySubUser, externalId);
        return bySubUser;
    }

    // 2) email eşleşmesi — başka IdP'den gelmiş olabilir
    if (email is not null)
    {
        var byEmailResult = await _uniquenessChecker.CheckEmailUniquenessAsync(email, ct);
        if (byEmailResult == UniquenessResult.AlreadyExists)
        {
            var existing = await _userRepository.FirstOrDefaultAsync(new UserByEmailSpecification(email), ct);
            if (existing is not null) { linkLogin(existing, externalId); existing.VerifyEmail(); return existing; }
        }
        // ExistsButNotVerified → DomainException
    }

    // 3) Yeni kullanıcı
    var newUser = new User(name, phone: null, email);
    if (email is not null) newUser.VerifyEmail();
    linkLogin(newUser, externalId);
    return newUser;
}
```

`linkLogin`, provider'a göre değişen bağlama davranışını parametre olarak alır:

```csharp theme={null}
public Task<User> GetOrCreateFromGoogleAsync(FullName name, Email? email, string googleUserId, CancellationToken ct)
    => GetOrCreateFromExternalAsync(
        AuthProviderType.Google, googleUserId, name, email,
        linkLogin: (u, sub) => u.AddOrUpdateGoogleLogin(sub, displayName: null),
        ct: ct);
```

<Tip>
  `ICitizenFactory`, `IUserFactory` ile **birebir aynı** arayüze sahiptir (vatandaş portalı için). Aynı oluşturma deseni iki ayrı aggregate'e tutarlı şekilde uygulanır.
</Tip>

## Domain Service: IUserUniquenessChecker

Bir kullanıcının e-posta/telefon/dış kimlik açısından benzersiz olup olmadığı, tek bir `User` örneğinin bilemeyeceği bir kuraldır — tüm kullanıcı kümesine bakmak gerekir. Bu yüzden bir domain service'tir.

```csharp theme={null}
public interface IUserUniquenessChecker
{
    Task<UniquenessResult> CheckEmailUniquenessAsync(Email email, CancellationToken ct);
    Task<UniquenessResult> CheckPhoneUniquenessAsync(Phone phone, CancellationToken ct);
    Task<(UniquenessResult, User?)> CheckExternalIdentityUniquenessAsync(AuthProviderType provider, string externalId, CancellationToken ct);
}

public enum UniquenessResult
{
    Success,               // benzersiz, oluşturulabilir
    AlreadyExists,         // doğrulanmış kayıt var
    ExistsButNotVerified   // kayıt var ama kanal doğrulanmamış
}
```

Üç-durumlu `UniquenessResult`, factory'nin kullanıcıya **anlamlı** mesaj verebilmesini sağlar: "zaten var, giriş yap" ile "var ama doğrulanmamış, doğrulamayı tamamla" farklı yönlendirmeler gerektirir.

## Domain Service: UserRegistrationService

Daha basit bir kayıt yolu sunan, repository üzerinden doğrudan uniqueness kontrolü yapıp `User` döndüren bir domain service:

```csharp theme={null}
public class UserRegistrationService
{
    private readonly IRepository<User> _userRepository;

    public async Task<User> RegisterNewUserAsync(FullName? name, Phone? phone = null, Email? email = null, CancellationToken ct = default)
    {
        if (phone == null && email == null)
            throw new DomainException("Kayıt için telefon veya e-posta gereklidir");

        if (phone != null)
        {
            var existing = await _userRepository.FirstOrDefaultAsync(new UserByPhoneNumberSpecification(phone), ct);
            if (existing is not null)
            {
                if (!existing.IsPhoneNumberVerified)
                    throw new DomainException("Bu telefon numarasına sahip bir kullanıcı zaten mevcut fakat üyelik onayı yapılmamış. Lütfen giriş yapınız");
                throw new DomainException("Bu telefon numarasına sahip bir kullanıcı zaten mevcut");
            }
        }
        // e-posta için benzer kontrol ...

        return new User(name, phone, email);
    }
}
```

## Ne zaman factory, ne zaman domain service?

<AccordionGroup>
  <Accordion title="Factory kullan — oluşturma karmaşıksa">
    Bir aggregate'i oluşturmak ek mantık gerektiriyorsa (uniqueness, dış IdP eşleştirme, hangi ctor/overload seçileceği, başlangıç event'leri) factory uygundur. Çıktısı **her zaman geçerli bir aggregate**'tir.
  </Accordion>

  <Accordion title="Domain service kullan — kural tek aggregate'e ait değilse">
    İş kuralı birden fazla aggregate'i, tüm bir koleksiyonu (uniqueness) veya repository sorgusunu ilgilendiriyorsa domain service'e koyun. Stateless'tır; durumu aggregate'lerde bırakır.
  </Accordion>

  <Accordion title="İkisi birlikte">
    Pratikte `UserFactory`, oluşturma orkestrasyonunu yapar ve kural değerlendirmesi için `IUserUniquenessChecker` domain service'ine **delege eder**. Sorumluluklar net ayrılır: factory "nasıl oluşturulur", service "izin var mı".
  </Accordion>
</AccordionGroup>

<Warning>
  Ne factory ne de domain service, kaydı **persist etmez**. Onlar yalnızca geçerli aggregate'i döndürür; repository'ye ekleme ve `UnitOfWork.SaveEntitiesAsync` çağrısı Application katmanındaki command handler'ın işidir. Böylece domain, transaction sınırından habersiz kalır.
</Warning>

## Sonraki adımlar

<CardGroup cols={2}>
  <Card title="Aggregate'ler" icon="cubes" href="/domain/aggregates">
    `internal` ctor ve `User` davranış metotları.
  </Card>

  <Card title="Domain Event'ler" icon="bolt" href="/domain/domain-events">
    Oluşturmada yayılan `UserCreatedDomainEvent`.
  </Card>

  <Card title="SharedKernel" icon="layer-group" href="/domain/shared-kernel">
    `IRepository`, `IReadRepository` ve specification altyapısı.
  </Card>

  <Card title="Domain'e genel bakış" icon="house" href="/domain/overview">
    Katmanın rolü ve klasör yapısı.
  </Card>
</CardGroup>
