2026'da Rails Service Objects: Tasarım Kalıpları, PORO ve Teknik Mülakat Soruları
PORO örnekleri, Result sınıfları ve temiz mimari kalıpları ile Rails service objects için kapsamlı rehber. Üretim uygulamaları için pratik kod.

Rails service objects, iş mantığını controller ve modellerden ayırarak Plain Old Ruby Objects (PORO) sınıflarına aktarır. Bu kalıp, Rails uygulamalarının büyüdükçe bakımını kolaylaştırır ve mülakatçılar adayların temiz mimari anlayışını değerlendirmek için bu konuyu sıkça test eder.
Bir service object tek bir iş operasyonunu kapsüller. Sınıf tek bir public metoda sahiptir (genellikle call), bağımlılıkları constructor üzerinden alır ve öngörülebilir bir sonuç döndürür. Bunu neden önemli olduğunu açıklayabilen adaylar, sadece nasıl uygulanacağını bilenlerden öne çıkar.
Service Objects Neden Fat Model ve Fat Controller Sorunlarını Çözer
Rails mantığın bir yere konulmasını teşvik eder. Yönlendirme olmadan, ekipler bunu modellere iter ("fat models, skinny controllers") ta ki ActiveRecord sınıfları binlerce satıra ulaşana kadar. Diğerleri bunu controllerlarda bırakır ve actionları izole test etmeyi imkansız hale getirir.
Service objects üçüncü bir yol sunar: domain mantığı, Rails'den açıkça ihtiyaç duydukları dışında hiçbir şeye bağımlı olmayan özel sınıflarda yaşar. Bu ayrışma, kodu tam framework yüklenmeden test edilebilir, farklı giriş noktaları arasında taşınabilir (controllerlar, arka plan işleri, konsol) ve her sınıf tek bir şey yaptığı için okunabilir kılar.
Rails Guides service objects önermez, ancak topluluk bu kalıp etrafında standartlaşmıştır. Rails 8'in basitliğe vurgusu, Solid Queue ve Solid Cache'in dış bağımlılıkların yerini alması ile PORO'ları daha da çekici kılar: daha az gem, daha fazla saf Ruby.
Üretime Hazır Bir Service Object'in Anatomisi
Bir service object üç şeye ihtiyaç duyar: bağımlılıkları alan bir constructor, tek bir public call metodu ve başarı veya başarısızlık sinyali veren bir dönüş tipi.
# app/services/users/create_account.rb
module Users
class CreateAccount
def initialize(user_repo: User, mailer: UserMailer)
@user_repo = user_repo
@mailer = mailer
end
def call(params)
user = @user_repo.new(params)
return Result.failure(:validation_failed, user.errors) unless user.valid?
user.save!
@mailer.welcome_email(user).deliver_later
Result.success(user)
rescue ActiveRecord::RecordNotUnique
Result.failure(:email_taken, "Email already registered")
end
end
endConstructor varsayılan değerlerle user_repo ve mailer kabul eder. Üretim kodu varsayılanları kullanır; testler mockları enjekte eder. call metodu doğrular, kaydeder, email gönderir ve her sonucu bir Result nesnesine sarar.
Gem Kullanmadan Minimal Bir Result Sınıfı Oluşturma
Bazı ekipler hemen dry-monads'a yönelir. Gem sağlamdır, ancak öğrenme eğrisi ekler. 30 satırlık bir Result sınıfı çoğu ihtiyacı karşılar:
# app/services/result.rb
class Result
attr_reader :value, :error, :code
def initialize(success:, value: nil, error: nil, code: nil)
@success = success
@value = value
@error = error
@code = code
end
def success? = @success
def failure? = !@success
def self.success(value) = new(success: true, value: value)
def self.failure(code, error = nil) = new(success: false, code: code, error: error)
def on_success
yield(value) if success?
self
end
def on_failure
yield(code, error) if failure?
self
end
endBu Result on_success ve on_failure ile zincirlemeyi destekler, yapılandırılmış hata kodları sunar ve sıfır bağımlılık gerektirir. Controllerlar bunu temiz bir şekilde tüketir:
# app/controllers/users_controller.rb
class UsersController < ApplicationController
def create
result = Users::CreateAccount.new.call(user_params)
result
.on_success { |user| redirect_to user, notice: "Account created" }
.on_failure { |code, error| render :new, status: :unprocessable_entity }
end
private
def user_params
params.require(:user).permit(:email, :password, :name)
end
endHalihazırda dry-rb ekosistemini kullanan veya birden fazla operasyonu zincirlemek için Do notasyonuna ihtiyaç duyan ekipler dry-monads'tan faydalanır. Gem, Ruby 3.x'te pattern matching desteği ile Success, Failure ve Maybe tiplerini sağlar.
Niyeti İşaret Eden İsimlendirme Kuralları
İki kural hakim:
| Stil | Örnek | Ne zaman kullanılır |
|---|---|---|
| Fiil cümlesi | CreateAccount, SendInvoice, RefundPayment | Durum değiştiren komutlar |
| -or sonekli isim | AccountCreator, InvoiceSender, PaymentRefunder | Daha az yaygın, bazı ekipler tercih eder |
Fiil cümlesi stili doğal okunur: Users::CreateAccount.new.call(params). -or soneki çalışır ama netlik katmadan hece ekler.
Dizin yapısı da önemlidir. Domain'e göre gruplama, ilgili service objectleri bir arada tutar:
app/services/
users/
create_account.rb
reset_password.rb
update_profile.rb
orders/
place_order.rb
cancel_order.rb
calculate_totals.rb
result.rbRuby on Rails mülakatlarında başarılı olmaya hazır mısın?
İnteraktif simülatörler, flashcards ve teknik testlerle pratik yap.
Test Edilebilir Service Objects için Dependency Injection
Sabit kodlanmış bağımlılıklar testi zorlaştırır. Bu service object veritabanına erişmeden ve gerçek emailler göndermeden test edilemez:
# Kaçının: sabit kodlanmış bağımlılıklar
class CreateAccount
def call(params)
user = User.create!(params) # Doğrudan model çağrısı
UserMailer.welcome(user).deliver # Doğrudan mailer çağrısı
end
endConstructor üzerinden enjeksiyon bunu çözer:
# Daha iyi: enjekte edilebilir bağımlılıklar
class CreateAccount
def initialize(user_repo: User, mailer: UserMailer)
@user_repo = user_repo
@mailer = mailer
end
def call(params)
user = @user_repo.create!(params)
@mailer.welcome(user).deliver_later
Result.success(user)
end
endTestler artık sahte nesneler kullanır:
# spec/services/users/create_account_spec.rb
RSpec.describe Users::CreateAccount do
let(:fake_repo) { class_double(User) }
let(:fake_mailer) { class_double(UserMailer) }
let(:service) { described_class.new(user_repo: fake_repo, mailer: fake_mailer) }
it "returns success with valid params" do
user = instance_double(User, valid?: true)
allow(fake_repo).to receive(:new).and_return(user)
allow(user).to receive(:save!)
allow(fake_mailer).to receive_message_chain(:welcome_email, :deliver_later)
result = service.call(email: "test@example.com", password: "secure123")
expect(result).to be_success
end
endVeritabanı yok, email yok, hızlı çalıştırma. RSpec dokümantasyonu tip doğrulamalı mocklar için class_double ve instance_doubleı kapsar.
Rails Service Objects Mülakat Soruları
Bu sorular mid-level ve senior Rails mülakatlarında karşımıza çıkar. Kod örnekleri ile somut cevaplar hazırlanmalıdır.
S: İş mantığı ne zaman modelde, ne zaman service objectte olmalı?
Modeller validasyonların, ilişkilerin, scopeların ve tek kayıt davranışlarının sahibidir. Service objectler birden fazla modeli kapsayan, dış çağrılar gerektiren veya açık transaction sınırlarına ihtiyaç duyan operasyonları yönetir. User modeli email formatını doğrular; CreateAccount servisi kullanıcıyı oluşturur, hoş geldiniz emaili gönderir ve varsayılan ayarları yapar.
S: Service objectlerde hatalar exception olmadan nasıl yönetilir?
Başarı/başarısızlık durumlarına sahip bir Result nesnesi döndürülür. Bu, hata yönetimini çağıran tarafta açık hale getirir, exception tabanlı kontrol akışından kaçınır ve farklı başarısızlık modları için yapılandırılmış hata kodları sağlar. Exceptionlar, iş kuralı ihlalleri için değil, gerçekten istisnai durumlar (veritabanı çökmesi, ağ hatası) için uygun kalır.
S: Service object ile interactor arasındaki fark nedir?
Interactorlar (interactor gibi gemlerden) belirli bir arayüze sahip service objectlerdir: bir context hash'i alırlar, onu değiştirirler ve context.fail! üzerinden başarısızlık sinyali verirler. PORO olarak service objectler önceden belirlenmiş bir arayüze sahip değildir, bu da ekiplere daha fazla esneklik ancak daha az tutarlılık sağlar.
S: Dış API çağıran bir service object nasıl test edilir?
HTTP istemcisi bir bağımlılık olarak enjekte edilir. Testlerde hazır yanıtlar döndüren bir stub geçirilir. WebMock veya VCR gibi araçlar HTTP etkileşimlerini kaydedip yeniden oynatabilir, ancak constructor enjeksiyonu birim testlerinde ağ çağrılarından tamamen kaçınır.
Daha fazla Rails mülakat hazırlığı için RSpec ve test kalıplarını kapsayan Rails mülakat soruları rehberine bakılabilir.
Karmaşık İş Akışları için Railway-Oriented Design
Railway-oriented programlama, bir iş akışını iki paralel ray olarak ele alır: başarı ileriye devam eder, başarısızlık hemen çıkar. Her adım ya başarı rayında ilerler ya da başarısızlık rayına geçer.
# app/services/orders/place_order.rb
module Orders
class PlaceOrder
def initialize(
inventory: InventoryService.new,
payment: PaymentService.new,
fulfillment: FulfillmentService.new
)
@inventory = inventory
@payment = payment
@fulfillment = fulfillment
end
def call(cart:, payment_method:)
validate_cart(cart)
.then { |items| reserve_inventory(items) }
.then { |reservation| charge_payment(reservation, payment_method) }
.then { |charge| create_fulfillment(charge) }
end
private
def validate_cart(cart)
return Result.failure(:empty_cart, "Cart is empty") if cart.items.empty?
Result.success(cart.items)
end
def reserve_inventory(items)
@inventory.reserve(items)
end
def charge_payment(reservation, payment_method)
result = @payment.charge(reservation.total, payment_method)
return result if result.failure?
Result.success({ reservation: reservation, charge: result.value })
end
def create_fulfillment(data)
@fulfillment.create(data[:reservation], data[:charge])
end
end
endResult sınıfı, yalnızca başarıda devam eden bir then metoduna ihtiyaç duyar:
# app/services/result.rb'ye ekle
def then
return self if failure?
yield(value)
endEnvanter rezervasyonu başarısız olursa, ödeme asla çalışmaz. Ödeme başarısız olursa, teslimat asla çalışmaz. Her adım bir Result döndürür ve zincir ilk başarısızlıkta kısa devre yapar.
Service Objects Ne Zaman Anti-Pattern Olur
Service objectler gerçek sorunları çözer ama gereksiz yere çoğalabilir. Bu uyarı işaretlerine dikkat edilmelidir:
-
Tek satırlık service objectler: Eğer service sadece bir model metodunu çağırıyorsa, değer katmadan dolaylılık ekler. Mantık basit olduğunda
User.authenticate(email, password),AuthenticateUser.new.call(email, password)dan daha iyidir. -
Sadece test için var olan service objectler: Bağımlılık enjeksiyonu testi kolaylaştırır, ancak yalnızca test edilebilirlik için model metodlarını sarmak amacıyla service objectler oluşturmak, gerçek sorunun mimari değil test kurulumu olduğunu gösterir.
-
Mantıksız anemik service objectler: Davranış eklemeden sadece modellere delege eden service objectler seremondir. Netguru mühendislik blogu bu aşırı çıkarma sorununu tartışır.
Alternatif: paylaşılan model davranışı için Concerns, Money veya DateRange gibi domain kavramları için Value Objects ve tek kayıt operasyonları için model metodları ile birlikte PORO'ların seçici kullanımı.
Rails Service Object Tasarımı için Temel Çıkarımlar
- Mantık birden fazla modeli kapsadığında, dış çağrılar gerektirdiğinde veya açık hata yönetimine ihtiyaç duyduğunda service objecte çıkarılır
- Ham değerler veya exceptionlar değil, Result nesnesi döndüren tek bir public
callmetodu kullanılır - Üretim kullanımı için makul varsayılanlarla constructor üzerinden bağımlılıklar enjekte edilir
- Service objectler eylemi tanımlayan
CreateAccountveyaPlaceOrdergibi fiil cümleleriyle isimlendirilir - Service objectler düz dizin yapısı yerine domain'e göre (
users/,orders/) organize edilir - dry-monads gibi gemler eklemeden önce minimal bir Result sınıfıyla başlanır
- Exceptionlar iş kuralı ihlalleri için değil, altyapı hataları için ayrılır
- Modellerin doğal olarak yönettiği tek satırlık operasyonlar için service object oluşturmaktan kaçınılır
Pratik yapmaya başla!
Mülakat simülatörleri ve teknik testlerle bilgini test et.
Ruby on Rails kodundaki hatayı bulabilir misin?
Gerçek bir kod parçası, gizli bir hata, günde bir deneme. Denemek için hesap gerekmez.

Yazan:
Anthony Fillion-MailletSharpSkill kurucusu
10 yılı aşkın süredir fullstack geliştirici. SharpSkill’i yönetiyor ve burada yayımlanan her şeyden sorumlu.
20 Ağustos 2026 tarihinde güncellendi
Paylaş
İlgili makaleler

2026'da Rails GraphQL API: graphql-ruby, Abonelikler ve Mülakat Soruları
Rails 8 ve graphql-ruby ile üretim düzeyinde GraphQL API oluşturma rehberi. Şema tasarımı, mutasyonlar, ActionCable ile abonelikler ve mülakat hazırlığı.

Rails 2026'da Arka Plan Görevleri: Sidekiq vs Good Job Karşılaştırması ve Mülakat Soruları
Rails 8'de Sidekiq, Good Job ve Solid Queue karşılaştırması. Performans analizi, özellik karşılaştırması ve Ruby on Rails background jobs konusunda sık sorulan mülakat soruları.

2026'da Rails Active Storage: Dosya Yükleme, S3 Entegrasyonu ve Mülakat Soruları
2026'da Rails Active Storage için kapsamlı rehber. S3 yapılandırması, doğrudan yükleme, görüntü işleme ve iş görüşmelerinde sıkça sorulan sorular.