O feature flag não roteia — ele toma a decisão. O proxy (YARP) executa o roteamento. São duas responsabilidades separadas.

Fluxo de decisão do hash

simnãoRequestHash(userId)result <= threshold?New SystemLegacyO hash garante que o mesmo userId sempre vai pro mesmo sistema

Como o % é calculado

O hash transforma qualquer GUID em um número de 0 a 99. A decisão é feita comparando esse número com o threshold.

// Passo a passo do hash var userId = Guid.NewGuid(); // 1. GUID → 16 bytes var bytes = userId.ToByteArray(); // ex: [0x55, 0x0e, 0x84, 0x00, ...] // 2. SHA256 → 32 bytes (determinístico em qualquer máquina) var hash = SHA256.HashData(bytes); // ex: [0x3A, 0xFF, 0xF9, 0x3A, 0xBB, ...] // 3. Primeiros 4 bytes → uint (sem sinal: nunca negativo, // sem o edge case Math.Abs(int.MinValue) → OverflowException) var raw = BitConverter.ToUInt32(hash, 0); // ex: 989.462.330 // 4. Módulo → 0..99 var hashValue = (int)(raw % 100); // ex: 30 // 5. Decisão bool goesToNew = hashValue < thresholdPercent; // 30 < 20 → false → Legacy // ⚠️ ToUInt32 % 100 e Math.Abs(ToInt32) % 100 NÃO são equivalentes: // divergem em ~metade dos hashes (bit de sinal). Trocar a fórmula no // meio do rollout re-bucketiza metade dos usuários — quebra o sticky. // Escolha uma fórmula antes do primeiro % e congele.

O operador % (módulo)

O módulo retorna o resto da divisão. Qualquer número não-negativo dividido por 100 tem resto entre 0 e 99. Em C#, % de int negativo dá resto negativo (-7 % 100 == -7) — por isso o código lê os bytes como uint (sem sinal, sem Math.Abs, sem o edge case de Math.Abs(int.MinValue) estourar).

ExpressãoDivisãoResto (resultado)Abaixo de 20?
7 % 1007 ÷ 100 = 0 resto7✓ New System
150 % 100150 ÷ 100 = 1 resto50✗ Legacy
989462330 % 100989462330 ÷ 100 = ... resto30✗ Legacy
1000 % 1001000 ÷ 100 = 10 resto0✓ New System
19 % 10019 ÷ 100 = 0 resto19✓ New System
20 % 10020 ÷ 100 = 0 resto20✗ Legacy

Por que SHA256 e não GetHashCode()?

GetHashCode() não é determinístico entre máquinas ou versões do .NET. Dois servidores podem calcular resultados diferentes para o mesmo GUID. O mesmo usuário cairia em sistemas diferentes a cada request.
MétodoDeterminístico?Entre máquinas?Usar em produção?
GetHashCode()Dentro do processoNãoNão
SHA256.HashData()SempreSimSim
LaunchDarkly / Azure AppConfigSempreSimRecomendado
❓ Por que o hash garante sticky behavior sem cookie ou sessão?
SHA256 é uma função pura — o mesmo input sempre produz o mesmo output. userId A sempre vai gerar o mesmo hashValue, então sempre vai pro mesmo sistema, sem precisar armazenar nada no cliente ou no servidor. É determinismo matemático.