Laravel Framework ile birçok proje geliştiriyoruz. Lakin büyük çaplı yani kalabalık projelerde Laravel üzerine geliştirmeler yapılması oldukça zor.
Birçok kesimden şu sözü duymuşsunuzdur: “Laravel kendi yapınızı uygulamanıza imkan vermiyor”. Lakin durum o şekilde değil. Tasarım desenleri ile profesyonel bir sistem geliştirdiğinizde Laravel’in aksine daha büyük imkan sağladığını görüyorsunuz. Diğer bir şekilde ifade etmek gerekirse, spagetti kodlamayla zaten tasarım desenlerine sahip bir yapı üzerine kendi yapınızı inşa etmek mümkün değil.
Lafı daha fazla uzatmadan konuya geçmek istiyorum yalnız şu hatırlatmayı yapmamda yarar var. Burada bahsedilen büyük ölçek 60-70’ten fazla model sınıflarının olması ve yapının bir çok küçük parçadan oluşmasıdır. Yaptığınız proje eğer küçük veya orta ölçekli ise Laravel’in kendi yapısının üzerine çıkmanıza gerek yok. Bu sizin üretkenliğinizi oldukça düşürecektir.
# Laravel’in Kendi Yapısı
Büyük ölçekli yapıda kullanabileceğimiz desenlere geçmeden önce Laravel’in kendi yapısını tekrar gözden geçirelim. Aşağıdaki yapı Laravel 5.6 sürümünden. Bu yapı hakkında kafanıza takılan bir kısım yoksa diğer konu başlığına hızlıca geçiş yapabilirsiniz.
.
├── app
│ ├── Broadcasting
│ ├── Console
│ ├── Events
│ ├── Exceptions
│ ├── Http
│ ├── Jobs
│ ├── Listeners
│ ├── Mail
│ ├── Notifications
│ ├── Policies
│ ├── Providers
│ ├── Rules
│ └── User.php
├── bootstrap
│ ├── app.php
│ └── cache
├── config
├── database
│ ├── factories
│ ├── migrations
│ └── seeds
├── public
├── resources
│ ├── assets
│ ├── lang
│ └── views
├── routes
│ ├── api.php
│ ├── channels.php
│ ├── console.php
│ └── web.php
├── storage
├── vendor
└── tests
/app Dizini
Burada belirtilen app
klasörü uygulama kodlarımızın bulunduğu dizindir.
/bootstrap Dizini
Çatımızın asıl başlangıç noktasıdır. Laravel Framework’ün kendi yapısını başlattığı ve cache klasörünü oluşturduğu kısımdır.
/config Dizini
İsminden de anlaşıldığı gibi ayarların tutulduğu dizindir. Ayar bilgileri bu dizindeki dosyalardan elde edilmektedir. Gizli(Secret) bilgileri de dotenv
aracılığıyla burada sisteme dahil edilmektedir.
/database Dizini
Veritabanı yapısı ile ilgili işlemlerin tutulduğu dizindir. migrations, factories ve seeds burada bulunmaktadır.
/public Dizini
Dış isteklerin yapıldığı başlangıç noktasıdır. Ek olarak isteklerde servis edilecek asset dosyaları (hazırlanmış css, js, resim dosyaları gibi) burada tutuyoruz.
/resources Dizini
resources
dizini presentation yani ön tarafta kullanıcıya göstereceğimiz verileri hazırladığımız kısımdır. views, derlenecek asset dosyaları ve dil dosyaları örneğin burada tutulur.
/routes Dizini
routes
dizini içerisinde herhangi bir ortamdan gelen isteğin nerede karşılık bulunduracağını belirtiriz. Burada ortamdan kasıt, web isteği olabildiği gibi websocket istekleri de olabilir.
/storage Dizini
storage
dizini içerisinde isminden de anlaşılabildiği gibi saklamak istediğimiz dosyaları tutmaktayız. Bunların içerisinde framework tarafından hazırlanan dosyalar(örneğin log) olduğu gibi kendimizin de tutmak istediği dosyalar(örneğin bir müzik sitesi için müzik dosyaları) da olabilir.
/tests Dizini
PHPUnit ile çalıştırmak istediğimiz testleri bu dizin içerisinde oluşturuyoruz.
/vendor Dizini
Composer bağımlılıklarımızın tutulduğu dizin.
/app/Broadcasting Dizini
Örneğin Websocket üzerinden yaptığımız gerçek zamanlı istekler/etkinlikler burada kontrol altında tutulmaktadır.
/app/Console Dizini
Artisan için oluşturduğunuz komutların tutulduğu dizindir. Bu komutlar tek seferlik işlemlerde kullanılabileceği gibi schedule hazırladığınız zamanlı görevler de olabilir.
/app/Events Dizini
Herhangi bir olay durumunda yaptırmak istediğiniz işlemleri tanımladığınız sınıfları tutan dizindir.
/app/Exceptions Dizini
Uygulamaya özel oluşturduğunuz hata sınıflarını oluşturduğunuz dizindir.
/app/Http Dizini
HTTP istekleriyle ilgili yapılması gerekenleri tutan dizindir. Controller, Middleware ve Requests burada tutulur.
/app/Jobs Dizini
Senkron veya Asenkron olarak tanımladığımız işleri burada tutmaktayız.
/app/Listeners Dizini
Oluşturduğunuz Eventler sonucunda yapılması gereken işlemleri burada tanımlamaktasınız. Örneğin UserLicenseFinished
etkinliği(event) sonucunda TerminateUser
ve SendTerminatedUserEmail
listenerlarını burada oluşturabilirsiniz.
/app/Mail Dizini
Uygulama tarafından gönderilecek e-postaları inşa edecek sınıfları bu dizinde tutmaktayız.
/app/Notifications Dizini
Çeşitli bildirim gönderme ortamları(SMS, E-Posta ve Slack gibi) için bildirimleri inşa ettiğimiz sınıfları oluşturduğumuz dizindir.
/app/Policies Dizini
Herhangi bir işlem için (Örneğin giriş veya api isteği) izin kontrol sınıflarını oluşturduğunuz dizindir.
/app/Providers Dizini
ServiceProviderları tanımladığınız dizindir.
/app/Rules Dizini
Validate etmek istediğiniz veriler için oluşturduğunuz sınıfları tutan dizindir.
# Biraz Daha Serbest
Orta ölçekli bir projede bu yapının üzerine ufak değişiklikler elbetteki yapıyoruz. Örneğin Models isminde bir namespace üzerinde model sınıfları ayrı bir şekilde tutuyoruz. Ek olarak Repositories ve Services de ekliyoruz.
Repository Pattern
Repository desenini tek bir makalede incelemek haksızlık olacaktır. Temel anlamda veri üzerinde işlem yapan kısım ile veriye erişen kısmın ayrılması diyebiliriz.
Buradaki Services kısmı ise Repository ile haberleşen kısmın da uygulama katmanından ayıran kısımdır. Bir örnek ile ilerlemek daha iyi olacaktır.
namespace App/Repositories;
interface UserInterface {
public function getUserById($userId);
public function getUserByUsername($username);
}
namespace App/Repositories;
class UserRepository implements UserInterface {
protected $model;
public function __construct(Model $model) {
$this->model = $model;
}
public function getUserById($userId) {
return $this->model->find($userId);
}
public function getUserByUsername($username) {
return $this->model->where('username', strtolower($username))->first();
}
}
namespace App/Services;
class UserService {
protected $repository;
public function __construct(UserInterface $repository){
$this->repository = $repository;
}
public function getUser($user) {
if(is_numeric($user))
return $this->repository->getUserById($user);
else
return $this->repository->getUserByUsername($user);
}
}
Bu örnek yapıda Service ve Repository kullanmış olduk. (Yapının çalışması için iki yapı için de ServiceProvider sınıflarını hazırlamanız gerekmektedir.)
Bu yapıda hiçbir sorun yok. Oldukça organize ve güzel bir yapı çıkmış oluyor. Lakin belirttiğim gibi orta ölçekli proje için uygun olacaktır. Büyük ölçekli projede bu yapı hayat kurtarmayacaktır.
Örneğin bu yapının sonunda Models dizinimize bakalım:
.
├── AklimaBaskaModelGelmedi.php
├── AnotherAnotherModel.php
├── AnotherModel.php
├── Comment.php
├── Gallery.php
├── Like.php
├── Note.php
├── Photo.php
├── Profile.php
├── Transaction.php
└── User.php
Uygulamamız üzerine eklediğimiz modüllerle bu Models dizini oldukça büyüyecektir. Bir alternatif olarak bu modülleri ayrı namespace üzerinde tutabilirsiniz. Lakin bir süre sonra bu da kurtarmayacaktır.
# Aklıma Bir Şeyler Geliyor Ama?
Bu aşamada aklınıza gelen yol büyük ihtimal, “Hadi o zaman projeyi microservislere ayıralım” olacaktır. Lakin bu iş o kadar kolay değil. Oluşturduğumuz büyük ölçekli yapıyı küçük küçük parçalara ayırmak oldukça zahmetli olacaktır. Temel olarak uygulayacağınız yol şu olur büyük ihtimal; Bir parçayı al ufak bir mikroservis uygulaması haline getir (apayrı bir proje olmuş oldu) ve uygulama üzerindeki projede gelen isteği bu mikroservise yönlendir. Bütün bu uğraş sonucunda da kazandığınız (!) sorunlar:
- Herhangi bir değişiklikte hata yapmanız muhtemel
- Yeni bir geliştirici anlamakta çok fazla zorlanacak ve ayrı ayrı parçalara bakmak zorunda kalacak.
- Çok fazla dosya
- Yeniden bir çözüm arayışı
# Bir Seçenek Daha Var O da “Domain Driven Design” Mı Dersin?
Elbette tek bir çözüm yolu yok. Yukarda bahsettiğim yapıları daha da geliştirebilir veya HMVC yapısına geçiş yapabilirsiniz. Her desenin + ve - leri var.
DDD’yi özetlemek gerekirse:
- Application yani uygulama: Controller, Middleware, Route
- Domain yani ana işi yapan işlev: Model, Repository, Policy vs.
- Infrastructure yani servisler: Email, Notification, Logging vs.
- Interface yani görünüş, gözüken kısım: Views, Lang, Assets nam-ı diğer Resources
Peki bu yapıda örnek Laravel dizini nasıl olabilir:
.
├── app
│ ├── Account (Domain)
│ │ ├── AccessControlLists.php
│ │ ├── Auth.php
│ │ ├── Console
│ │ ├── Jobs
│ │ ├── Listeners
│ │ ├── Repositories
│ │ ├── Models
│ │ └── Validators
│ ├── Http (Application)
│ ├── Infrastructure
│ │ ├── Events
│ │ └── Exceptions
│ ├── Gallery (Domain)
│ └── Post (Domain)
├── bootstrap
│ ├── app.php
│ └── cache
├── config
├── database
│ ├── factories
│ ├── migrations
│ └── seeds
├── public
├── resources (Interface)
│ ├── assets
│ ├── lang
│ └── views
├── routes
│ ├── api.php
│ ├── channels.php
│ ├── console.php
│ └── web.php
├── storage
├── vendor
└── tests
Fark edebileceğiniz üzere DDD’de sadece namespace eklemekten başka bir durum yok.
# Sen Pala Vere Bırak Sonuç Göster
Sonuç yapıyı göstermek istersek şöyle bir yapı uygun olacaktır:
.
├── app
│ ├── Account
│ │ ├── AccessControlLists.php
│ │ ├── Auth.php
│ │ ├── Console
│ │ ├── Events
│ │ ├── Exceptions
│ │ ├── Jobs
│ │ ├── Listeners
│ │ ├── Models
│ │ ├── Repositories
│ │ └── Validators
│ ├── Gallery
│ ├── Http
│ │ ├── Controllers
│ │ └── Middleware
│ ├── Post
│ └── Providers
├── bootstrap
│ ├── app.php
│ └── cache
├── config
├── database
│ ├── factories
│ ├── migrations
│ └── seeds
├── public
├── resources
│ ├── assets
│ ├── lang
│ └── views
├── routes
│ ├── api.php
│ ├── channels.php
│ ├── console.php
│ └── web.php
├── storage
├── vendor
└── tests
Elbette çok büyük ölçekte bir Laravel Uygulamasında bu yapı da yeterli olmayacaktır.
Şimdi bir senaryo kuralım. Projemizde Authentication kısmı mikroservis yapılması gerektiği ile ilgili bir karar alındı. Bu durumda bu yapıdaki bir projede tek yapılması gereken Account
(domain) dizininin mikroservis olarak ayrılması. Komple bir değişiklik yapmaya gerek kalmadan sadece o modülü mikroservis çok rahat yapabiliriz. Çünkü domain dışındaki diğer tüm sınıflar AccessControlLists
ve Auth
sınıflarını biliyor ve kullanıyor. Bunları tutmak yeterli olacaktır. Mikroservisi hazırladıktan sonra ana projemizdeki dizin yapısı şu şekilde olacaktır:
.
├── app
│ ├── Account
│ │ ├── AccessControlLists.php
│ │ └── Auth.php
│ ├── Gallery
│ ├── Http
│ │ ├── Controllers
│ │ └── Middleware
│ ├── Post
│ └── Providers
├── bootstrap
│ ├── app.php
│ └── cache
├── config
├── database
│ ├── factories
│ ├── migrations
│ └── seeds
├── public
├── resources
│ ├── assets
│ ├── lang
│ └── views
├── routes
│ ├── api.php
│ ├── channels.php
│ ├── console.php
│ └── web.php
├── storage
├── vendor
└── tests
Gördüğünüz gibi uygulama kodlarımız içerisinde bir değişiklik yapmadan modül olarak ayırmış ve mikroservis haline getirmiş olduk. Buradaki AccessControlLists
ve Auth
mikroservisimizle haberleşecektir.
Dikkat edilmesi gereken kısım ise, bu gibi senaryolara sahip olabileceğimiz için Domain içerisindeki haberleşen Service
sınıflarımızın temel veri türlerini kullanması gerektiğidir. Örneğin, array
, string
, int
ve boolean
gibi. Herhangi bir sınıf belirtmeniz ayırma işlemi sonucunda sorun çıkartacaktır. Çünkü o sınıfı tanımıyor ve bilmiyor olacaktır.
# Bitiş
Yazı içerisinde elbetteki yanlışlarım olmuş olabilir. Yazıyı düzenleyerek hataları gidererek daha iyi bir makale haline getirebiliriz. Eklenmesini istediğiniz kısımları da yine aynı şekilde düzenleyerek pull request atabilirsiniz.