Ordu merkezli 95 mağazalı bir perakende zinciri (gıda + temizlik), POS verisi 4 farklı sistem (mağaza tipi başına ayrı), e-ticaret Shopify, CRM Dynamics, ERP SAP B1 — analytics için her seferinde ayrı ayrı çekip Excel’de birleştirme yapıyordu. 6 ayda Microsoft Fabric’e geçildi. Bu yazı OneLake-tabanlı mimarinin sahadaki teknik notlarını paylaşıyor.
Eski Mimari (SaaS Salatası)
| Sistem | Veri | Erişim |
|---|---|---|
| POS Tipi A (40 mağaza) | Günlük satış, stok | SFTP CSV |
| POS Tipi B (35 mağaza) | Günlük satış, stok | API REST |
| POS Tipi C (20 mağaza) | Günlük satış, stok | SQL Server export |
| E-ticaret Shopify | Online sipariş, müşteri | API GraphQL |
| Dynamics CRM | Müşteri profil, kampanya | OData |
| SAP B1 | Stok, satınalma, finans | HANA SQL |
Sonuç: aylık satış raporu hazırlamak ~~3 gün manuel work, hata oranı yüksek, gerçek zamanlı görüş yok.
Microsoft Fabric Mimarisi
[Veri Kaynakları]
POS A (CSV) ─┐
POS B (API) ─┤
POS C (SQL) ─┼──► [Dataflow Gen2 (Fabric)]
Shopify ─┤ ↓
Dynamics ─┤ [OneLake Lakehouse - Bronze]
SAP B1 ─┘ ↓
[Notebook (Spark) - Silver]
↓
[Notebook - Gold (curated)]
↓
[Semantic Model]
↓
[Power BI Direct Lake - 95 dashboard]
Fabric Capacity
F-SKU seçimi: F32 (32 capacity unit, ~$5K/ay) — başlangıç. Pause özelliği var (gece kapatılırsa fatura azalır).
Lakehouse Katmanları (Bronze/Silver/Gold)
Bronze: Ham Veri
# Notebook: bronze-layer-pos-a.ipynb
from pyspark.sql.functions import current_timestamp, lit
# Lakehouse'a Bronze tablosu yaz
df_pos_a_raw = spark.read.csv(
"abfss://retail@onelake.dfs.fabric.microsoft.com/Files/landing/pos_a/2026/04/25/*.csv",
header=True,
inferSchema=True
)
df_bronze = df_pos_a_raw
.withColumn("ingested_at", current_timestamp())
.withColumn("source_system", lit("POS_A"))
df_bronze.write.format("delta")
.mode("append")
.saveAsTable("bronze.pos_a_sales_raw")
Silver: Temizlenmiş + Birleştirilmiş
# Notebook: silver-layer-sales-unified.ipynb
from pyspark.sql.functions import col, regexp_replace, to_date
# 3 POS sistem birleşik schema
sales_a = spark.table("bronze.pos_a_sales_raw").select(
col("magaza_id").alias("store_id"),
col("urun_kod").alias("product_code"),
to_date(col("satis_tarih"), "dd.MM.yyyy").alias("sale_date"),
col("adet").cast("integer").alias("quantity"),
col("tutar").cast("decimal(18,2)").alias("amount_try"),
lit("POS_A").alias("source")
)
sales_b = spark.table("bronze.pos_b_sales_raw").select(...) # benzer mapping
sales_c = spark.table("bronze.pos_c_sales_raw").select(...)
unified = sales_a.unionByName(sales_b).unionByName(sales_c)
# Stok master ile join (ürün kategorisi enrich)
products = spark.table("silver.dim_product")
sales_enriched = unified.join(products, "product_code", "left")
sales_enriched.write.format("delta")
.mode("overwrite")
.saveAsTable("silver.fact_sales")
Gold: Aggregate + Business-Ready
# Notebook: gold-layer-monthly-store-summary.ipynb
monthly_summary = spark.sql("""
SELECT
s.store_id,
st.store_name,
st.region,
DATE_TRUNC('month', s.sale_date) AS month,
p.category,
COUNT(DISTINCT s.transaction_id) AS transaction_count,
SUM(s.quantity) AS total_quantity,
SUM(s.amount_try) AS total_revenue_try,
SUM(s.amount_try) / COUNT(DISTINCT s.transaction_id) AS avg_basket
FROM silver.fact_sales s
JOIN silver.dim_store st ON s.store_id = st.store_id
JOIN silver.dim_product p ON s.product_code = p.product_code
WHERE s.sale_date >= DATE_SUB(CURRENT_DATE(), 730)
GROUP BY s.store_id, st.store_name, st.region,
DATE_TRUNC('month', s.sale_date), p.category
""")
monthly_summary.write.format("delta")
.mode("overwrite")
.partitionBy("month")
.saveAsTable("gold.monthly_store_category_summary")
Pipeline Orchestration
Data Pipeline (Fabric):
- Trigger: günlük 03:00 (POS gün kapanışı sonrası)
- Activity 1: Copy CSV → Bronze (POS A)
- Activity 2: API call → Bronze (POS B)
- Activity 3: SQL extract → Bronze (POS C)
- Activity 4: Notebook silver-layer-sales-unified
- Activity 5: Notebook gold-layer-monthly-summary
- Activity 6: Refresh semantic model
- Activity 7: Email summary report
- On failure: Teams webhook alert
Direct Lake Mode (Power BI)
| Mode | Veri kopyası | Refresh | Performans |
|---|---|---|---|
| Import | Var (Power BI’a kopyalanır) | Manuel/scheduled | Hızlı, ama refresh sınırı |
| DirectQuery | Yok | Real-time query | Yavaş büyük veri |
| Direct Lake | Yok (OneLake’i direkt okur) | Anlık (Delta files) | Hızlı + güncel |
Direct Lake = Fabric’e özel, OneLake’teki Delta tablosunu Power BI semantic model olarak kullanır. Performance Import’a yakın, freshness DirectQuery gibi.
Üretilen Power BI Dashboard’lar
- CEO daily KPI (95 mağaza top-line)
- Mağaza müdürü performans (kendi mağazası vs benzer)
- Kategori müdürü stok devir hızı + sezonluk trend
- E-ticaret funnel + cart abandonment
- Müşteri segmentasyon (RFM analizi)
- Promosyon ROI
Sonuçlar (6 Ay)
| Metrik | Önce | Sonra |
|---|---|---|
| Aylık rapor hazırlama süresi | ~~3 gün manuel | ~~5 dk (otomatik refresh) |
| Veri freshness | ~~aylık | ~~günlük (gece batch) |
| Mağaza müdürü self-service raporlama | Yok | %80 müdür kendi raporunu çekiyor |
| Out-of-stock cevap süresi | ~~3-4 gün | ~~ertesi gün |
| Promosyon ROI doğruluk | %~~60 (kabaca) | %~~95 (item-level attribution) |
Sahada Düşülen Üç Tuzak
- F-SKU’yu over-provision etmek: F64 → F32’ye düşürülünce performans aynı, fatura yarı. Önce baseline + monitor + scale.
- Bronze/Silver/Gold ayrımını skip etmek: Tüm transformation tek notebook’ta = bakım kabusu. Medallion architecture şart.
- Capacity’i 7/24 açık tutmak: Production capacity gece pause edilebilir (batch hariç). %~~30-40 fatura tasarrufu.
CloudSpark olarak Microsoft Fabric mimarisi, OneLake + Lakehouse tasarımı, medallion architecture, Spark notebook geliştirme, Direct Lake semantic model ve Power BI dashboard’lar için danışmanlık veriyoruz.



