Yaygın modülerleştirme kalıpları

Tüm projelere uygun tek bir modülerleştirme stratejisi yoktur. Gradle'ın esnek yapısı nedeniyle bir projeyi nasıl organize edeceğinizle ilgili çok az kısıtlama vardır. Bu sayfada, çok modüllü Android uygulamaları geliştirirken kullanabileceğiniz bazı genel kurallar ve yaygın kalıplara genel bir bakış sunulmaktadır.

Yüksek kohezyon ve düşük kuplaj ilkesi

Modüler bir kod tabanını karakterize etmenin bir yolu, kuplaj ve kohezyon özelliklerini kullanmaktır. Bağlantı, modüllerin birbirlerine bağlı olduğu dereceyi ölçer. Bu bağlamda tutarlılık, tek bir modülün öğelerinin işlevsel olarak nasıl ilişkili olduğunu ölçer. Genel bir kural olarak, düşük bağlantı ve yüksek tutarlılık için çaba göstermeniz gerekir:

  • Düşük bağlantı, modüllerin birbirinden mümkün olduğunca bağımsız olması gerektiği anlamına gelir. Böylece bir modülde yapılan değişikliklerin diğer modüller üzerindeki etkisi sıfır veya minimum düzeyde olur. Modüller, diğer modüllerin iç işleyişi hakkında bilgi sahibi olmamalıdır.
  • Yüksek tutarlılık, modüllerin sistem gibi çalışan bir kod koleksiyonundan oluşması gerektiği anlamına gelir. Sorumlulukları açıkça tanımlanmış olmalı ve belirli alan bilgilerinin sınırları içinde kalmalıdırlar. Örnek bir e-kitap uygulamasını ele alalım. İki farklı işlevsel alan olduğu için kitap ve ödemeyle ilgili kodların aynı modülde bir arada kullanılması uygun olmayabilir.

Modül türleri

Modüllerinizi düzenleme şekliniz büyük ölçüde uygulamanızın mimarisine bağlıdır. Aşağıda, önerilen uygulama mimarimizi uygularken uygulamanızda kullanabileceğiniz bazı yaygın modül türleri verilmiştir.

Veri modülleri

Veri modülü genellikle bir depo, veri kaynakları ve model sınıflarını içerir. Veri modülünün üç temel sorumluluğu şunlardır:

  1. Belirli bir alanın tüm verilerini ve iş mantığını kapsama: Her veri modülü, belirli bir alanı temsil eden verilerin işlenmesinden sorumlu olmalıdır. Birbiriyle ilişkili oldukları sürece birçok veri türünü işleyebilir.
  2. Depoyu harici bir API olarak sunun: Veri modülünün herkese açık API'si, verilerin uygulamanın geri kalanına gösterilmesinden sorumlu olduğundan veri modülünün herkese açık API'si bir depo olmalıdır.
  3. Tüm uygulama ayrıntılarını ve veri kaynaklarını dışarıdan gizleyin: Veri kaynaklarına yalnızca aynı modüldeki depolar tarafından erişilebilir. Dışarıdan gizli kalırlar. Kotlin'in private veya internal görünürlük anahtar kelimesini kullanarak bu durumu zorunlu kılabilirsiniz.
Şekil 1. Örnek veri modülleri ve içerikleri.

Özellik modülleri

Özellik, genellikle kayıt veya ödeme akışı gibi yakından alakalı bir dizi ekrana veya uygulamaya karşılık gelen, uygulama işlevselliğinin ayrı bir parçasıdır. Uygulamanızda alt gezinme çubuğu varsa her hedef muhtemelen bir özelliktir.

Şekil 2. Bu uygulamanın her sekmesi bir özellik olarak tanımlanabilir.

Özellikler, uygulamanızdaki ekranlar veya hedeflerle ilişkilendirilir. Bu nedenle, mantıklarını ve durumlarını yönetmek için ilişkilendirilmiş bir kullanıcı arayüzü ve ViewModel olabilir. Tek bir özelliğin tek bir görünüm veya gezinme hedefiyle sınırlı olması gerekmez. Özellik modülleri veri modüllerine bağlıdır.

Şekil 3. Örnek özellik modülleri ve içerikleri.

Uygulama modülleri

Uygulama modülleri, uygulamaya giriş noktasıdır. Bunlar, özellik modüllerine bağlıdır ve genellikle kök gezinme sağlarlar. Oluşturma varyantları sayesinde tek bir uygulama modülü, bir dizi farklı ikili koda derlenebilir.

Şekil 4. *Demo* ve *Tam* ürün çeşidi modülleri bağımlılık grafiği.

Uygulamanız otomobil, giysi veya TV gibi birden çok cihaz türünü hedefliyorsa her biri için bir uygulama modülü tanımlayın. Bu, platforma özgü bağımlılıkları ayırmaya yardımcı olur.

Şekil 5. Wear uygulaması bağımlılık grafiği.

Sık kullanılan modüller

Temel modüller olarak da bilinen yaygın modüller, diğer modüllerin sıklıkla kullandığı kodları içerir. Yedekliliği azaltır ve bir uygulamanın mimarisinde belirli bir katmanı temsil etmez. Yaygın olarak kullanılan modüllere dair örnekleri aşağıda bulabilirsiniz:

  • Kullanıcı arayüzü modülü: Uygulamanızda özel kullanıcı arayüzü öğeleri veya ayrıntılı marka bilinci oluşturma öğeleri kullanıyorsanız tüm özelliklerin yeniden kullanılması için widget koleksiyonunuzu bir modüle yerleştirmeniz gerekir. Bu, kullanıcı arayüzünüzün farklı özelliklerde tutarlı olmasına yardımcı olabilir. Örneğin, temasınız merkeziyse yeniden markalama gerçekleştiğinde zahmetli bir yeniden düzenlemenin önüne geçebilirsiniz.
  • Analytics modülü: İzleme, genellikle yazılım mimarisi üzerinde çok az dikkate alınarak işletme gereksinimlerine göre belirlenir. Analytics izleyiciler, genellikle birçok alakasız bileşende kullanılır. Sizin için böyle bir durum söz konusuysa, özel bir analiz modülünün olması iyi bir fikir olabilir.
  • Ağ modülü: Birçok modül ağ bağlantısı gerektirdiğinde, http istemcisi sağlamak için özel bir modüle sahip olmayı düşünebilirsiniz. Bu, özellikle istemciniz özel yapılandırma gerektirdiğinde kullanışlıdır.
  • Yardımcı program modülü: Yardımcılar olarak da bilinen yardımcı programlar, genellikle uygulama genelinde yeniden kullanılan küçük kod parçalarıdır. Yardımcı programlara örnek olarak test yardımcıları, para birimi biçimlendirme işlevi, e-posta doğrulayıcı veya özel operatör verilebilir.

Test modülleri

Test modülleri yalnızca test amacıyla kullanılan Android modülleridir. Modüller, yalnızca test çalıştırmak için gerekli olan ve uygulamanın çalışma zamanında gerekli olmayan test kodu, test kaynakları ve test bağımlılıkları içerir. Test modülleri, teste özel kodu ana uygulamadan ayırmak için oluşturulduğundan, modül kodunun yönetimi ve bakımı kolaylaşır.

Test modüllerinin kullanım alanları

Aşağıdaki örneklerde test modüllerini uygulamanın özellikle faydalı olabileceği durumlar gösterilmiştir:

  • Paylaşılan test kodu: Projenizde birden çok modül varsa ve test kodunun bir kısmı birden fazla modüle uygulanabiliyorsa kodu paylaşmak için bir test modülü oluşturabilirsiniz. Bu, tekrarı azaltmaya yardımcı olabilir ve test kodunuzun bakımını kolaylaştırabilir. Paylaşılan test kodu, özel onaylar veya eşleyiciler gibi yardımcı program sınıfları veya işlevlerin yanı sıra simüle edilmiş JSON yanıtları gibi test verilerini içerebilir.

  • Temiz Derleme Yapılandırmaları: Test modülleri, kendi build.gradle dosyalarına sahip olabildikleri için daha temiz derleme yapılandırmalarına sahip olmanızı sağlar. Uygulama modülünüzün build.gradle dosyasını yalnızca testlerle ilgili yapılandırmalarla karıştırmanız gerekmez.

  • Entegrasyon Testleri: Test modülleri; kullanıcı arayüzü, iş mantığı, ağ istekleri ve veritabanı sorguları dahil olmak üzere uygulamanızın farklı bölümleri arasındaki etkileşimleri test etmek için kullanılan entegrasyon testlerini depolamak için kullanılabilir.

  • Büyük ölçekli uygulamalar: Test modülleri, karmaşık kod tabanlarına ve birden fazla modüle sahip büyük ölçekli uygulamalar için özellikle yararlıdır. Bu gibi durumlarda test modülleri, kod organizasyonunu ve sürdürülebilirliğini iyileştirmeye yardımcı olabilir.

Şekil 6. Test modülleri, birbirine bağımlı olacak modülleri izole etmek için kullanılabilir.

Modülden modüle iletişim

Modüller nadiren toplam bir ayrım halinde ortaya çıkar ve genellikle diğer modüllere ihtiyaç duyar ve onlarla iletişim kurar. Modüller birlikte çalışırken ve sık sık bilgi alışverişinde bulunurken bile bağlantıyı düşük tutmak önemlidir. Bazen iki modül arasında doğrudan iletişim, mimari kısıtlamalarında olduğu gibi istenmeyebilir. Bu da döngüsel bağımlılıklarda olduğu gibi imkansız olabilir.

Şekil 7. Döngüsel bağımlılıklar nedeniyle modüller arasında doğrudan, iki yönlü bir iletişim mümkün değildir. Diğer iki bağımsız modül arasındaki veri akışını koordine etmek için bir uyumlulaştırma modülü gereklidir.

Bu sorunu gidermek için diğer iki modül arasında aracılık yapan üçüncü bir modüle sahip olabilirsiniz. Arabulucu modülü, her iki modülden gelen mesajları dinleyebilir ve gerektiğinde yönlendirebilir. Örnek uygulamamızda, etkinlik farklı bir özelliğin parçası olan ayrı bir ekranda gerçekleşmiş olsa bile, ödeme ekranının hangi kitabın satın alınacağını bilmesi gerekir. Bu durumda aracı, gezinme grafiğinin sahibi olan modüldür (genellikle bir uygulama modülü). Örnekte, Gezinme bileşenini kullanarak verileri ana sayfa özelliğinden ödeme özelliğine iletmek için gezinmeyi kullanıyoruz.

navController.navigate("checkout/$bookId")

Ödeme hedefi, kitapla ilgili bilgileri getirmek için bağımsız değişken olarak bir kitap kimliği alır. Bir hedef özelliğin ViewModel içindeki gezinme bağımsız değişkenlerini almak için kayıtlı durum tutma yerini kullanabilirsiniz.

class CheckoutViewModel(savedStateHandle: SavedStateHandle, …) : ViewModel() {

   val uiState: StateFlow<CheckoutUiState> =
      savedStateHandle.getStateFlow<String>("bookId", "").map { bookId ->
          // produce UI state calling bookRepository.getBook(bookId)
      }
      …
}

Nesneleri, gezinme bağımsız değişkenleri olarak iletmemeniz gerekir. Bunun yerine, özelliklerin istenen kaynaklara veri katmanından erişip yüklemek için kullanabileceği basit kimlikler kullanın. Bu şekilde, bağlantı kurmayı düşük tutabilir ve tek doğru kaynak ilkesini ihlal etmemiş olursunuz.

Aşağıdaki örnekte her iki özellik modülü de aynı veri modülüne bağlıdır. Bu, arabulucu modülünün iletmesi gereken veri miktarını en aza indirmeyi mümkün kılar ve modüller arasındaki bağlantıyı düşük tutar. Modüller, nesneleri iletmek yerine temel kimlikleri değiş tokuş etmeli ve kaynakları paylaşılan bir veri modülünden yüklemelidir.

Şekil 8. Paylaşılan bir veri modülünü temel alan iki özellik modülü.

Bağımlılığı ters çevirme

Bağımlılığı ters çevirme, kodunuzu soyutlamanın somut bir uygulamadan ayrı kalacak şekilde düzenlemesidir.

  • Soyutlama: Uygulamanızdaki bileşenlerin veya modüllerin birbiriyle nasıl etkileşimde bulunduğunu tanımlayan sözleşme. Soyutlama modülleri sisteminizin API'sini tanımlar ve arayüzler ve modeller içerir.
  • Soyut uygulama: Soyutlama modülüne bağlı olan ve bir soyutlama davranışını uygulayan modüller.

Soyutlama modülünde tanımlanan davranışa dayanan modüller, belirli uygulamalar yerine yalnızca soyutlamanın kendisine dayalı olmalıdır.

Şekil 9. Üst seviye modüller ve uygulama modülleri, doğrudan alt seviye modüllere bağlı olan üst seviye modüller yerine soyutlama modülüne bağlıdır.

Örnek

Çalışması için veritabanına ihtiyacı olan bir özellik modülü düşünün. Özellik modülü, veritabanının nasıl uygulandığıyla ilgilenmemektedir; yerel bir Room veritabanı veya bir uzak Firestore örneği olabilir. Yalnızca uygulama verilerini depolayıp okuması gerekir.

Bunu başarmak için özellik modülü, belirli bir veritabanı uygulamasına değil soyutlama modülüne bağlıdır. Bu soyutlama, uygulamanın veritabanı API'sini tanımlar. Başka bir deyişle, veritabanıyla nasıl etkileşim kurulacağını belirleyen kuralları belirler. Bu, özellik modülünün temel uygulama ayrıntılarını öğrenmeye gerek kalmadan herhangi bir veritabanını kullanmasına olanak tanır.

Somut uygulama modülü, soyutlama modülünde tanımlanan API'lerin gerçek uygulamasını sağlar. Bunu yapabilmek için uygulama modülü, soyutlama modülüne de ihtiyaç duyar.

Bağımlılık ekleme

Şimdiye kadar, özellik modülünün uygulama modülüyle nasıl bağlantılı olduğunu merak ediyor olabilirsiniz. Cevap Bağımlılık Yerleştirme'dir. Özellik modülü, gerekli veritabanı örneğini doğrudan oluşturmaz. Bunun yerine, hangi bağımlılıkları gerektirdiğini belirtir. Daha sonra bu bağımlılıklar harici olarak, genellikle uygulama modülünde sağlanır.

releaseImplementation(project(":database:impl:firestore"))

debugImplementation(project(":database:impl:room"))

androidTestImplementation(project(":database:impl:mock"))

Avantajlar

API'lerinizi ve uygulamalarını birbirinden ayırmanın avantajları şunlardır:

  • Değiştirilebilirlik: API ve uygulama modüllerinin net bir şekilde birbirinden ayrılması sayesinde, aynı API için birden fazla uygulama geliştirebilir ve API'yi kullanan kodu değiştirmeden bunlar arasında geçiş yapabilirsiniz. Bu, özellikle farklı bağlamlarda farklı beceriler veya davranışlar sağlamak istediğiniz senaryolarda faydalı olabilir. Örneğin, test için örnek uygulama ile gerçek bir üretim uygulaması karşılaştırması.
  • Ayrıştırma: Bu ayırma, soyutlama kullanan modüllerin belirli bir teknolojiye bağlı olmadığı anlamına gelir. Veritabanınızı daha sonra Oda yerine Firestore olarak değiştirmeyi tercih ederseniz değişiklikler yalnızca işi yapan belirli modülde (uygulama modülü) gerçekleşeceğinden ve veritabanınızın API'sini kullanan diğer modülleri etkilemeyeceğinden daha kolay olur.
  • Test edilebilirlik: API'leri uygulamalarından ayırmak, test sürecini büyük ölçüde kolaylaştırabilir. API sözleşmelerine aykırı test durumları yazabilirsiniz. Ayrıca, örnek uygulamalar dahil çeşitli senaryoları ve uç durumları test etmek için farklı uygulamalar da kullanabilirsiniz.
  • Daha iyi derleme performansı: Bir API'yi ve uygulamasını farklı modüllere ayırdığınızda, uygulama modülündeki değişiklikler, derleme sistemini API modülüne bağlı olarak modülleri yeniden derlemeye zorlamaz. Bu durum, özellikle derleme sürelerinin önemli olabileceği büyük projelerde, derleme sürelerini kısaltır ve üretkenliği artırır.

Ne zaman ayırmalısınız?

Aşağıdaki durumlarda API'lerinizi uygulamalarından ayırmanız önerilir:

  • Çok çeşitli özellikler: Sisteminizin parçalarını çeşitli şekillerde uygulayabiliyorsanız anlaşılır bir API, farklı uygulamaların birbirinin yerine kullanılmasına olanak tanır. Örneğin, OpenGL veya Vulkan kullanan bir oluşturma sisteminiz ya da Play veya şirket içi faturalandırma API'nizle çalışan bir faturalandırma sisteminiz olabilir.
  • Birden fazla uygulama: Farklı platformlar için paylaşılan özelliklere sahip birden fazla uygulama geliştiriyorsanız ortak API'ler tanımlayabilir ve her platform için özel uygulamalar geliştirebilirsiniz.
  • Bağımsız ekipler: Bu ayrım, farklı geliştiricilerin veya ekiplerin kod tabanının farklı bölümleri üzerinde aynı anda çalışmasına olanak tanır. Geliştiriciler API sözleşmelerini anlamaya ve bunları doğru şekilde kullanmaya odaklanmalıdır. Diğer modüllerin uygulama ayrıntıları hakkında endişelenmeleri gerekmiyor.
  • Büyük kod tabanı: Kod tabanı büyük veya karmaşık olduğunda API'nin uygulamadan ayrılması, kodu daha yönetilebilir hale getirir. Kod tabanını daha ayrıntılı, anlaşılır ve sürdürülebilir birimlere ayırmanızı sağlar.

Nasıl uygulanır?

Bağımlılığı ters çevirme işlevini uygulamak için aşağıdaki adımları izleyin:

  1. Bir soyutlama modülü oluşturun: Bu modülde, özelliğinizin davranışını tanımlayan API'ler (arayüzler ve modeller) bulunmalıdır.
  2. Uygulama modülleri oluşturma: Uygulama modülleri, API modülüne dayanmalı ve bir soyutlama davranışını uygulamalıdır.
    Üst seviye modüller ve uygulama modülleri, doğrudan alt seviye modüllere bağlı olan üst seviye modüller yerine soyutlama modülüne bağlıdır.
    Şekil 10. Uygulama modülleri soyutlama modülüne bağlıdır.
  3. Üst düzey modülleri soyutlama modüllerine bağımlı hale getirin: Doğrudan belirli bir uygulamaya bağlı olmak yerine modüllerinizi soyutlama modüllerine bağımlı hale getirin. Üst düzey modüllerin uygulama ayrıntılarını bilmesi gerekmez, yalnızca sözleşmeye (API) ihtiyaç duyar.
    Üst düzey modüller, uygulamaya değil soyutlamalara bağlıdır.
    Şekil 11. Üst düzey modüller, uygulamaya değil soyutlamalara dayanır.
  4. Uygulama modülünü sağlayın: Son olarak, bağımlılıklarınız için gerçek uygulamayı sağlamanız gerekir. Özel uygulama, proje ayarlarınıza göre değişir, ancak uygulama modülü genellikle bunu yapmak için iyi bir yerdir. Uygulamayı sağlamak için bunu seçtiğiniz derleme varyantına veya test kaynağı grubuna bağımlılık olarak belirtin.
    Uygulama modülü gerçek uygulamayı sağlar.
    Şekil 12. Uygulama modülü, gerçek uygulamayı sağlar.

Genel en iyi uygulamalar

Başta da belirttiğimiz gibi çok modüllü bir uygulama geliştirmenin tek bir doğru yolu yoktur. Birçok yazılım mimarisi olduğu gibi bir uygulamayı modüler hale getirmenin çeşitli yolları vardır. Bununla birlikte, aşağıdaki genel öneriler kodunuzu daha okunabilir, sürdürülebilir ve test edilebilir hale getirmenize yardımcı olabilir.

Yapılandırmanızın tutarlı olmasını sağlayın

Her modül yapılandırma ek yüküne neden olur. Modül sayısı belirli bir eşiğe ulaşırsa tutarlı yapılandırmayı yönetmek zorlaşır. Örneğin, modüllerin aynı sürümün bağımlılıklarını kullanması önemlidir. Bağımlılık bir sürümünü yükseltmek için çok sayıda modülü güncellemeniz gerekiyorsa bu yalnızca çaba değil, aynı zamanda olası hatalar için fırsattır. Bu sorunu çözmek için yapılandırmanızı merkezileştirmek amacıyla gradle araçlarından birini kullanabilirsiniz:

  • Sürüm katalogları, senkronizasyon sırasında Gradle tarafından oluşturulan bağımlılıkların tür güvenli listesidir. Tüm bağımlılıklarınızı beyan edebileceğiniz merkezi bir yerdir ve projedeki tüm modüller için kullanılabilir.
  • Modüller arasında derleme mantığını paylaşmak için kural eklentileri kullanın.

Mümkün olduğunca az gösterin

Modülün herkese açık arayüzü minimum düzeyde olmalı ve yalnızca gerekli olanları göstermelidir. Uygulama ayrıntılarını dışarıya sızdırmamalıdır. Her şeyi mümkün olan en küçük kapsamla inceleyin. Bildirileri modülü gizli yapmak için Kotlin’in private veya internal görünürlük kapsamını kullanın. Modülünüzde bağımlılıkları tanımlarken api yerine implementation tercih edin. İkincisi ise modülünüzün tüketicilerine geçişli bağımlılıklar getirir. Uygulama kullanmak, yeniden oluşturulması gereken modüllerin sayısını azalttığından derleme süresini kısaltabilir.

Kotlin ve Java modüllerini tercih et

Android Studio'nun desteklediği üç temel modül türü vardır:

  • Uygulama modülleri uygulamanız için giriş noktasıdır. Bunlar kaynak kodu, kaynaklar, öğeler ve bir AndroidManifest.xml içerebilir. Uygulama modülünün çıkışı, Android App Bundle (AAB) veya Android Uygulama Paketi (APK) olur.
  • Kitaplık modülleri, uygulama modülleriyle aynı içeriğe sahiptir. Bunlar, diğer Android modülleri tarafından bağımlılık olarak kullanılır. Kitaplık modülünün çıktısı, Android Arşivi'ne (AAR) benzerdir. Yapısal olarak uygulama modülleriyle aynıdır ancak bu dosyalar daha sonra diğer modüller tarafından bağımlılık olarak kullanılabilecek bir Android Arşivi (AAR) dosyası olarak derlenir. Kitaplık modülü, aynı mantığı ve kaynakları birçok uygulama modülünde kapsayabilir ve yeniden kullanmak mümkün olur.
  • Kotlin ve Java kitaplıkları herhangi bir Android kaynağı, öğesi veya manifest dosyası içermez.

Android modülleri ek yük ile geldiğinden, tercihen mümkün olduğunca Kotlin veya Java türünü kullanmak istersiniz.