Hakkımda
İstanbul'da backend geliştiricisiyim. Bir ürünün altında duran şeyi yazıyorum: gerçek zamanlı arka uçlar, çok kiracılı platformlar, onları besleyen hatlar ve ekibin hepsini içinden yönettiği paneller. Üç ürün, dördüncüsünün içinden çıkardığım bir substratın üzerinde duruyor. Oyun tarafında da istemciyi ve sunucuyu birlikte yazıyorum.
Nasıl çalışıyorum
- Kararı yaz
- Kod neyin yapıldığını söyler. Karar kaydı nedenini söyler, ve bir yıl sonra birinin ihtiyacı olan kısım odur. Genelde de o kararı geri alıp almamayı düşünürken. Sayıyı aşağıdaki kart, depoları tarayarak veriyor.
- Yolun tamamını üstlen
- Bir özellik pull request'te bitmiyor. Onu üç sınırdan geçirip en iyisini ummaktansa şemadan ekrana, oradan da yayına çıkaran deploy'a kadar kendim taşımayı tercih ederim.
- Aracı yaz
- Aynı iş elle iki kez yapıldıysa artık bir araçtır. Bir oyundaki denge hattı ile işteki operasyon paneli, farklı problemlere çevrilmiş aynı refleks.
- Sıkıcı olanı seç
- Problemi çözen en az sürpriz şeyi seçerim, kazandığım eforu da işin gerçekten zor olduğu yere harcarım.
Neyle geliştiriyorum
- Oyun
- İstemcide Unity ve C#, sunucuda .NET ve PostgreSQL, aralarında EF Core.
- Ürün
- PHP ve TypeScript, Slim ve NestJS, MySQL, PostgreSQL ve Redis. WebSocket daemon'ları HTTP uygulamasının içinde değil yanında duruyor. İş bir istek değil bir hat olduğunda Python'a geçiyorum.
- Kurumsal
- Bun üzerinde NestJS, Next.js, Postgres ve MySQL üzerinde Drizzle ile MikroORM, izinleri tutan CASL ve raporlamanın gerektirdiği her şey.
Birlikte çalışmak
Çalıştığım ekip dört mühendis. Üçü mobil istemcilerde, ben ağırlıkla arka uçtayım. Sesli sohbet platformunda gerçek zamanlı ses sağlayıcısını değiştirirken bu iş API'nin benim tarafımda bitmiyordu. O yüzden changelog gönderip beklemek yerine o migration boyunca onların deposuna, onların dalına commit attım.
- Tek başıma
- Bir işin tek mühendisi olmak, yazdığım dokümanın miktarını değil ne kadar erken yazıldığını değiştiriyor. Sonraki kişinin tek iş arkadaşı o doküman olacak, o yüzden gerekçe hâlâ tazeyken yazılıyor; bir çeyrek sonra hatırlanarak değil.
GitHub'da
- Karar kaydı
- 1.011
Bir ADR yalnızca başkası okuyacağı için yazılır. Bunlar 38 depoya yayılıyor.
Yıla göre açılan depo
TypeScript 33%C# 31.9%JavaScript 11.4%HTML 10.3%Python 7.5%SCSS 2.5%Diğer 3.4%
13 public sealed record LootResult(bool Dropped, RolledItem? Item, CurrencyDrop? Currency);21 public static class LootPipeline22 {23 // Stateless components, safe to share across all ops (ADR-0020 component interpreter).24 private static readonly IReadOnlyDictionary<string, ILootComponent> Registry =25 new Dictionary<string, ILootComponent>26 {27 ["gate"] = new GateComponent(),28 ["loot-class"] = new LootClassComponent(),29 ["rarity-roll"] = new RarityRollComponent(),30 ["type-roll"] = new TypeRollComponent(),31 ["class-roll"] = new ClassRollComponent(),32 ["item-level"] = new ItemLevelComponent(),33 ["mod-fill"] = new ModFillComponent(),34 ["currency-roll"] = new CurrencyRollComponent(),35 ["bonus-rolls"] = new BonusRollsComponent(),36 };3738 // Class routing (SPEC-REWARD-SPINE §4): loot-class decides which branch's components apply. Skipping a39 // component skips its rng draws too — both toolchains run this identical routing, so parity holds.40 private static readonly HashSet<string> ItemOnly =41 new HashSet<string> { "rarity-roll", "type-roll", "class-roll", "item-level", "mod-fill" };49 private static readonly HashSet<string> Deferred =50 new HashSet<string> { "unique-roll", "pity" };54 public static LootResult Execute(ConfigPipeline pipeline, RollContext ctx, IRandom rng, KernelConfig cfg)55 {56 foreach (var stage in pipeline.Stages)57 {58 if (!Registry.TryGetValue(stage.Component, out var component))59 {60 if (Deferred.Contains(stage.Component)) continue; // valid but unimplemented in Slice 1 → no-op61 throw new System.InvalidOperationException($"unknown loot component '{stage.Component}'");62 }6364 // "unique" is item-bearing: unique-roll is still deferred, so a unique draw resolves as a normal item65 // roll until its slice lands (it refines ctx.Item rather than replacing the branch).66 var itemBearing = ctx.LootClass == "item" || ctx.LootClass == "unique";67 if (ItemOnly.Contains(stage.Component) && !itemBearing) continue;68 if (stage.Component == "currency-roll" && ctx.LootClass != "currency") continue;6970 component.Apply(ctx, rng, cfg, stage.Params);71 if (!ctx.Dropped)72 return new LootResult(false, null, null);73 }7475 // Enforce the LootResult invariant rather than only documenting it: a drop MUST carry something. Reachable76 // from authored data alone — e.g. a pipeline weighting `currency` but omitting the currency-roll stage would77 // otherwise return Dropped:true with no mints, silently dropping a reward on the floor.78 if (ctx.Dropped && ctx.Item == null && ctx.Currency == null)79 throw new System.InvalidOperationException(80 $"loot class '{ctx.LootClass}' dropped but produced neither an item nor currency — " +81 "the pipeline is missing the stage that resolves this class");8283 return new LootResult(ctx.Dropped, ctx.Item, ctx.Currency);84 }Toorn-shared039f617dotnet/Game.Kernel/Loot/LootPipeline.csSatır 13 – 8455 satır
public sealed record LootResult(bool Dropped, RolledItem? Item, CurrencyDrop? Currency);
public static class LootPipeline
{
// Stateless components, safe to share across all ops (ADR-0020 component interpreter).
private static readonly IReadOnlyDictionary<string, ILootComponent> Registry =
new Dictionary<string, ILootComponent>
{
["gate"] = new GateComponent(),
["loot-class"] = new LootClassComponent(),
["rarity-roll"] = new RarityRollComponent(),
["type-roll"] = new TypeRollComponent(),
["class-roll"] = new ClassRollComponent(),
["item-level"] = new ItemLevelComponent(),
["mod-fill"] = new ModFillComponent(),
["currency-roll"] = new CurrencyRollComponent(),
["bonus-rolls"] = new BonusRollsComponent(),
};
// Class routing (SPEC-REWARD-SPINE §4): loot-class decides which branch's components apply. Skipping a
// component skips its rng draws too — both toolchains run this identical routing, so parity holds.
private static readonly HashSet<string> ItemOnly =
new HashSet<string> { "rarity-roll", "type-roll", "class-roll", "item-level", "mod-fill" };
private static readonly HashSet<string> Deferred =
new HashSet<string> { "unique-roll", "pity" };
public static LootResult Execute(ConfigPipeline pipeline, RollContext ctx, IRandom rng, KernelConfig cfg)
{
foreach (var stage in pipeline.Stages)
{
if (!Registry.TryGetValue(stage.Component, out var component))
{
if (Deferred.Contains(stage.Component)) continue; // valid but unimplemented in Slice 1 → no-op
throw new System.InvalidOperationException($"unknown loot component '{stage.Component}'");
}
// "unique" is item-bearing: unique-roll is still deferred, so a unique draw resolves as a normal item
// roll until its slice lands (it refines ctx.Item rather than replacing the branch).
var itemBearing = ctx.LootClass == "item" || ctx.LootClass == "unique";
if (ItemOnly.Contains(stage.Component) && !itemBearing) continue;
if (stage.Component == "currency-roll" && ctx.LootClass != "currency") continue;
component.Apply(ctx, rng, cfg, stage.Params);
if (!ctx.Dropped)
return new LootResult(false, null, null);
}
// Enforce the LootResult invariant rather than only documenting it: a drop MUST carry something. Reachable
// from authored data alone — e.g. a pipeline weighting `currency` but omitting the currency-roll stage would
// otherwise return Dropped:true with no mints, silently dropping a reward on the floor.
if (ctx.Dropped && ctx.Item == null && ctx.Currency == null)
throw new System.InvalidOperationException(
$"loot class '{ctx.LootClass}' dropped but produced neither an item nor currency — " +
"the pipeline is missing the stage that resolves this class");
return new LootResult(ctx.Dropped, ctx.Item, ctx.Currency);
}github.com/TrojanVoid5,1 yıl boyunca 9 hesapta 60 depo. Çoğu özel ya da bir kuruma ait; bu sayfa rakamları o yüzden taşıyor.
Ölçüm 2026-09-08