Micro Frontend Mimarisi: Derinlemesine Bir Bakış Ve Uygulama Örnekleri

Furkan Emmezoğlu
10 min readDec 6, 2022

--

English Version

Micro Frontend, uygulama ara yüzünü parçalayarak ayrı projeler haline getiren bir mimari yaklaşım olarak karşımıza çıkıyor.

Monolitik mimarideki ara yüz projeleri tek bir proje içerisine yazılan algoritmalar ile gerçekleştiriliyordu, Micro Frontend mimarisi ile artık bu ara yüzler ayrı projeler haline getirilerek bağımsız bir şekilde geliştirilebilir hale geldiler.

Şekil 1 - MF Mimarisi

Şekil 1 üzerinden örnek vermek gerekirse shared, micro-app1, micro-app2, micro-app3 olmak üzere 4 ayrı proje birbirinden tamamen ayrı ekipler tarafından en uygun teknoloji seçilerek geliştirilebilir.

Şekildeki app1 üzerinde çalışan ekip React tercih etmiş ve diğer ekiplerden izole bir şekilde app1’e ait iş modelini uygulamaktayken, app2 ve app3 Angular, Vue gibi başka frameworkler ile devam etmişlerdir.

Temel Çalışma Prensibi?

Şekil 2 - Temel Çalışma Prensibi

Micro Frontend mimarisinin uygulanması için iFrame, Webpack Module Federation, manifest gibi birtakım yaklaşımlar uygulanabiliyor.

Not: Yazının ilerleyen kısımlarında ilgili yöntemlerinden detaylıca bahsedeceğiz.

Temel çalışma prensibinden bahsedecek olursak, Micro Frontend ayrı ayrı geliştirilmiş projeler ve bu projeleri çağırıp üzerinden gösterecek bir ana proje olarak dizayn edilip çalıştırılır.

Örnek vermek gerekirse bir Micro Frontend sitesine gittiğinizde ilgili URL sizi ana projeye yönlendirir, Ana proje ise uygulanan yönteme göre diğer projeleri çağırıp ilgili kısımlara yerleştirir. Böylece bütün bir sistem uçtan uca oluşacaktır.

Avantajları Neler?

1. Projeyi küçük parçalara ayırdığımız için her bir parçanın scale edilebilmesi daha kolay olacaktır.

2. Her ekip işin bir parçasına odaklandığı için domain daha iyi anlaşılıyor ve odak dağılmıyor olacaktır.

3. Kod parçaları daha küçük yapılar haline geldiği için ekibe yeni katılan birisi koda daha çabuk uyum sağlayabilecektir.

4. Her ekip kendisine en uygun teknolojileri seçebilecektir. Örneğin A ekibi Angular kullanırken, B ekibi React kullanabilir.

5. Projenin bir parçasında yaşanan bir hatadan diğer parçalar etkilenmiyor olacaktır.

6. Progressive rendering ile uygulamanın hızı artacaktır.

7. Ekipler birbirinden bağımsız deployment yapabilecek ve CI/CD süreçleri hızlanacaktır.

8. Her proje kendi içinde test edileceğinden, daha küçük parçalara daha kapsamlı testler daha kolay yazılacaktır.

Dezavantajları ve Sıkıntıları Neler? Çözümleri Neler?

1. CSS Çakışması

Micro Frontend tasarlanırken dikkat edilmesi gereken konulardan biri de csslere vereceğimiz isimlerdir.

Örneğin A ve B ekipleri kendi projelerinde .container class isminde tanımlama yaparlar. Bu projeler ana projede render edildiğinde ise cssler çakışacak ve bir ekibin yazdığı CSS geçersiz olacaktır.

Çözüm olarak ekipler kendilerine prefix/suffix belirleyip bu şekilde css tanımlamaları yapabilirler.

Diğer bir çözüm yöntemi olarak styled component/css module gibi kütüphaneler kullanılabilir, hashlenmiş class isimleri ile bu sorun çözülebilir.

Not: Ekiplerin kullanacağı CSS’ler de tutarlı olmalı (Benzer renklerin kullanılması gibi).

2. Aynı kütüphanelerin farklı versiyonlarının kullanılması

Şekil 3 - React Versionları

Micro Frontend yapısı sayesinde A ekibi React 15 ile geliştirme yaparken B ekibi React 16 ile geliştirme yapabilir. Micro Frontend’in bu yapısı bazı durumlar için avantaj olabilirken, kullanıcının browser’ına hem React v15 hem de React v16 yüklemek sistemi yavaşlatacaktır.

Çözüm olarak ilgili kütüphaneleri shared olarak kullanmayı sağlayacak package manager tooları kullanabiliriz veya ekipler anlaşıp aynı versiyonu kullanabilirler.

Diğer bir yöntem olarak da webpack-modül-federation ile configler ayarlanarak bu sorunun üstesinden gelinilebilir. Yazının devamında bu çözümle ilgili detay vereceğiz.

3. Projeler Arası İletişim

Micro Frontend konuşurken teoride her ne kadar izole ekipler konuşulsa da iş uygulamaya gelince projeler arasında bağımlılık oluşabiliyor.

Örneğin session gibi her iki projenin de kullanacağı paylaşımlı veriler veya bir projenin çıktısının diğer projede kullanılması gerektiği gibi bağımlılıklar olabiliyor.

Çözüm olarak bu bağımlılıkların ekipler arasında güçlü bir iletişim ile yönetilmesi öneriliyor.

3.1. Aynı API isteklerinin ele alınması

Hem A projesi hem de B projesi bir API’ye ayrı ayrı aynı isteği gönderiyorsa bu performans sıkıntılarına yol açabilir.

Bu tip request’lerin belirlenip tek bir projeden gönderilip, verinin paylaşımlı olarak kullanılması uygun olacaktır.

3.2. Aynı UI componentleri’nin kullanımı

UI üzerinden gösterilecek ortak bileşenler tespit edilmez ise gereksiz bir geliştirme maliyeti ve duplicate kod problemi olarak karşımıza çıkabilir.

Ortak component’ları belirleyip ve storybook gibi yapılar ile paylaşımlı olarak kullanılması daha uygun olacaktır.

3.3.Kod tekrarları

Tıpkı UI bileşenlerinde olduğu gibi kod içerisinde de ekiplerin geliştirdiği benzer algoritmalar olabilir.

Örneğin time ile ilgili yazılmış olan bir helper class’ının iki ekip tarafından ayrı ayrı yazılması gerekebilir. Bu da hem efor bakımından hem de duplicate kod açısından sıkıntılar yaratabilir.

Çözüm olarak bu tip algoritmaların belirlenip ekipler tarafından yönetilmesi öneriliyor.

Micro Frontend Mimarileri

Şekil 4 - MF Mimarileri

Micro frontend mimarileri 5 başlık altında incelebilir. Her başlık kendi içinde avantaj ve dezavantajlara sahiptir.

Mimari seçerken önemli olan iş modeline uygun olanı seçmektir. Sektöre baktığımızda ise yaygın olarak Webpack Modüle Federation kullanıldığını görebiliriz.

Burada önemli olan diğer bir nokta da Micro frontend mimarisinin gerekliliğidir.

Micro frontend mimarisi avantajlarını birbirinden farklı domain alanının birleşmesi ile oluşan sistemlerde gösterir.

Eğer sisteminiz tek bir domaine hizmet ediyor ve tek bir ekiple çalışıyorsanız micro frontend uygulayarak overengineering yapıyor olabilirsiniz.

1. Server Side Template İle

Server üzerinde oluşturulan index.html’in client’a gönderilmesi ile oluşan mimaridir. Bu mimaride Micro Frontend mantığı ile tasarlanan html dosyaları plug in şeklinde index.html’e entegre olarak client’a gönderilir. Entegre olacak parçalar birinden bağımsız domain spesifik olarak geliştirilebilir.

Avantajları

  • En önemli avantajı browser’a derlenmiş bir şekilde hazır sayfalar gönderilir. Bu sebeple daha hızlı yükleme elde edilebilir.
  • Server side template ile SEO konusunda da avantaj elde edilebilir.

Dezavantajları

  • Server tarafındaki mimarinin karmaşıklığı dezavantaj olarak karşınıza çıkabilir. Sistem büyüdükçe scale etmek zorlaşır.
  • Sunucu kapasitesi ve maliyeti de dezavantaj olabilir.

2. Build-time Entegrasyonu

Herhangi bir Micro Frontend uygulamasının paket haline getirilip, 3. Parti bir kütüphane gibi ana uygulamaya eklenmesi ile oluşturulan mimaridir.

Örneğin listeleme sayfasının bir parçası olan filtreleme component’i paket haline getirilip, ana projeye eklenip kullanılırsa package.json aşağıdaki gibi oluşacaktır.

{ 
"name": "@e-commerce/container",
"version": "1.0.0",
"description": "A product delivery app",
"dependencies": {
"@e-commerce/filter": "^1.2.3",
}
}

Avantajları

  • Genel olarak uygulaması basit ve bilindik bir yöntemdir.
  • Lazy load destekler ve uygulamanın performansını artırır.

Dezavantajları

  • Micro frontend’lerde yapılan değişikler sonucunda versiyon yükseltip, her projede deployment çıkmak gerekir.
  • Versiyon yönetimi ayrıca bir iş yükü getirir.

3. Iframe Ile Runtime Entegrasyonu

Iframe eskilerden beri tarayıcılar üzerinde kullanımda olan bir DOM elemanıdır. Iframe ile basit bir şekilde uygulamalarınızı bir projede birleştirip, Micro frontend mimarisini uygulayabiliriz.

Birbirinden bağımsız deploy edilen uygulamaları, ana bir projede aşağıdaki gibi Iframe üzerinde gösterebiliriz.

<iframe src="https://www.trendyol.com" title="Trendyol"></iframe>

Avantajları

  • Genel olarak uygulaması basit ve bilindik bir yöntemdir.
  • Projeler arasında izolasyon sağlamak kolaydır.

Dezavantajları

  • Micro Frontend’lerin içerisinde belirlenen routing yapıları, iframe yöntemi ile uyumsuz çalışabilir.
  • Diğer mimarilere oranla daha yavaş yükleme perfomansı görebiliriz.

4. Web Components Entegrasyonu

Micro Frontend ile run time’da mimari kurmanın bir yolu da Micro Frontend’leri script ile ana projeye eklemektir. Daha sonra custom web component oluşturularak Micro Frontend’ler render edilirler.

<script src="https://product.example.com/bundle.js"></script>
<script src="https://order.example.com/bundle.js"></script>
<script src="https://profile.example.com/bundle.js"></script>
<div id="micro-frontend-root"></div>
<script type="text/javascript">
// These element types are defined by the above scripts
const webComponentsByRoute = {
'/': 'micro-frontend-product-store',
'/order-food': 'micro-frontend-order-product', '/user-profile': 'micro-frontend-user-profile',
};
const webComponentType = webComponents ByRoute [window.location.pathname];
// Having determined the right web component custom element type,
// we now create an instance of it and attach it to the document
const root = document.getElementById('micro-frontend-root');
const webComponent = document.createElement(webComponentType);
root.appendChild (webComponent);

Avantajları

  • Bağımsız deploymentı destekler.
  • Sadece ihtiyaç dahilindeki bundle’lar yüklenerek hızlı yükleme performansı görebiliriz.

Dezavantajları

  • Tüm bundle’ların tek seferde yüklendiği durumda diğer mimarilere oranla daha yavaş yükleme perfomansı gösterir.
  • Kullanıcı analitiklerini toplamak için daha çok efor sarf etmek gerekecektir.

5. Webpack Federation

Webpack modüle federation yöntemi ile Micro Frontend mimarisine girmeden önce webpack nedir kısaca bilmekte yarar var.

Şekil 5 - Webpack

Webpack, sizin uygulamanızı bağımlı olduğu paketler ile build edip sunulabilir bir paket haline getiren bir modüldür.

Webpack React, Angular, Vue gibi kütüphanelerle ile kullanılabilir.

Örneğin React ile geliştirdiğiniz bir uygulamayı webpack ile build edip index.html, index.js ve bağımlı olduğumuz kütüphanelerin build edilmiş dosyalarını elde edebiliriz. Böylece single page application mantığı ile uygulamamızı kolayca deploy edebiliriz.

Webpack Konfigürasyonları

Webpack ile build ederken default ayarlarda build edebileceğimiz gibi konfigürasyonları da özelleştirebiliriz.

  • Entry: Webpack’e, projenin bağımlılık grafiğini nereden başlayarak oluşturması gerektiğini bildirilir. Default olarak src/index.js ayarlıdır.
  • Output: Build edilen dosyanın hangi dizinde ve isimde olacağını belirttiğimiz yerdir. Default olarak ./dist/main.js şeklinde build dosyası oluşturur.
  • Loaders: Webpack, default olarak sadece JS ve JSON dosyalarını işleyebilmektedir. Eğer Sass, Text gibi farklı türlerinde build olmasını istiyorsak bunları tanımlamamız gerekmektedir.
  • Mode: Webpack default olarak production için build olsa da development ve none ortamları içinde build oluşturabilmektedir.
  • Plugins: Loaders kısmında webpack’in default olarak sadece JS ve JSON build ettiğinden bahsetmiştik. Eğer build sonucunda elimizde HTML gibi dosyalarında olmasını istiyorsak ilgili pluginlerin config dosyasında tanımlanması gerekmektedir. Pluginlerin diğer bir örneği de Micro Frontend alt yapısını kurmamızı sağlayacak Webpack Modüle Federation pluginidir.
// webpack.config.js
module.exports = {
entry: 'index.js',
output: {
path: path.resolve(__dirname, './dist'),
filename: 'index_bundle.js'
},
module: {
rules: [ // Loaders
{ test: /\.txt$/, use: 'raw-loader' }
]
}
plugins: [new HtmlWebpackPlugin({template: './src/index.html'})]
};

Modüle Federation Plugin

Webpack, modüle federation plugini ile runtime’da Micro Frontend’leri birbirine bağlamayı mümkün kılar.

Uygulama kolaylığı, bağımlılıkların kolayca paylaşılabilir olması, frameworkten bağımsız kullanılabiliyor olması, routing sorunlarını kolayca çözebildiği gibi sebeplerden dolayı sektörde Micro Frontend mimarisi için en iyi seçeneklerden biri olarak karşımıza çıkar. Bu sebeple yazımın devamında da Modüle federation konseptini görebileceğimiz bir uygulama yaparak daha da detaya inebiliriz.

Uygulama içeriğinde Micro Frontend’leri birleştirecek bir shell projesi, bir inputtan aldığı kelimeyi arama yapacak bir search projesi ve listeleme yapacak bir listing projesi düşünelim.

Framework detayına çok takılmamak adına 3 proje için de React kullanabiliriz.

Bu proje için iki farklı ekip kurulup bir ekibin sadece search domaini ile ilgilenip bu projeye geliştirme yaptığını, diğer ekibin ise sadece listeleme domaini ile ilgilendiğini düşünebiliriz.

Uygulamamız için öncelikle React projelerini oluşturalım.

Shell App: npx create-react-app shell
Search App: npx create-react-app search
List App: npx create-react-app list

Uygulamaları oluşturduktan sonra modüle federation için işimize yarayacak bağımlılıkları projelerimize ekleyelim.

yarn add webpack webpack-cli webpack-server html-webpack-plugin css-loader style-loader babel-loader webpack-dev-server

Micro Frontend’leri asenkron bir şekilde yüklemek için 3 projede de bootstrap.js isminde file oluşturalım.

Bootstrap.js içerisine index.js dosyasının içeriğini kopyalayıp, index.js içerisinde sadece bu dosyayı import edelim. Bu şekilde asenkron yükleme yapabiliriz.

// bootstrap.js
import React from 'react';
import ReactDOM from 'react-dom/client';
import './index.css';
import App from './App';

const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<React.StrictMode>
<App />
</React.StrictMode>
);
// index.js
import('../src/bootstrap')

Bu aşamada artık uygulamalarımız webpack konfigürasyonlarını hazırlamak için uygun durumda.

Webpack konfigürasyonları için uygulamaların root klasörlerine webpack.config.js isimli dosyaları oluşturalım.

// search-app/ webpack.config.js
const HtmlWebpackPlugin = require('html-webpack-plugin');
const ModuleFederationPlugin = require('webpack/lib/container/ModuleFederationPlugin');
const deps = require('./package.json').dependencies;
module.exports = {
/*Mode kısmı development veya production verilebilir.
Bu parametreye göre webpack build hassasiyetini ayarlayacaktır.*/
mode: 'development',
devServer: { // Application port
port: 3001,
},
/*Modül içerisinde js dosyalarını derlemesi için
babel ve css dosyaları için css-loader ve
style-loader modüllerini eklemeliyiz. */
module: {
rules: [
{
test: /\.js?$/,
exclude: /node_modules/,
loader: 'babel-loader',
options: {
presets: [
'@babel/preset-env',
'@babel/preset-react',
],
},
},
{
test: /\.css$/i,
use: ["style-loader", "css-loader"],
},
],
},
/*Micro Frontend mimarimizi kurgulamayı
sağlayacak webpack modüle federation pluginide buraya eklenmeli.*/
plugins: [
new ModuleFederationPlugin(
{
name: 'SEARCH',
filename: 'remoteEntry.js',
exposes: { // export component
'./RemoteSelectorForInput': './src/components/Input'
},
shared: [
{
...deps,
react: { requiredVersion: deps.react, singleton: true, eager: true },
'react-dom': {
requiredVersion: deps['react-dom'],
singleton: true,
}
},
],
}
),
/*index.html çıksını alabilmek için HtmlWebpackPlugini eklemek gerekmektedir.*/
new HtmlWebpackPlugin({
template:
'./public/index.html',
}),
],
};

Bu kısma kadar 3 projede de aynı geliştirmeler yapıldı. Bu aşamadan sonra modüle federation plugini farklılık gösterecektir.

Artık search ve list projelerinden component’leri export edip Shell projesinde gösterebiliriz.

  • name: Diğer projelerden bu plugini seçmek için kullanılır. Örneğin Shell projesi bu plugini name@URL/fileName olarak çağıracaktır.
  • filename: Diğer projelerin build sonucu oluşan JS dosyasını bulabilmesi için verilen isimdir. Bir çeşit projeye giriş dosyasıdır. Örneğin name@URL/fileName
  • exposes: Export edeceğimiz component’leri tanımladığımız yerdir. Buradaki JSON’da key remote selector, value ise export edilecek componentin path’i verilir. Key kısmına dilediğimiz ismi verebiliriz. Önemli olan import ederken aynı ismi kullanmaktır.
const Search = React.lazy(
() => import('SEARCH/RemoteSelectorForInput')
);
const List = React.lazy(
() => import('LIST/RemoteSelectorForList')
);
  • shared: Paylaşımlı olarak kullanacağımız bağımlılıkları tanımladığımız yerdir. Daha fazla detay için link.
// list-app/ webpack.config.js
plugins: [
new ModuleFederationPlugin(
{
name: 'LIST', // remote selector name
filename: 'remoteEntry.js',// remote entry file
exposes: { // export component
'./RemoteSelectorForList': './src/components/list'// remoteKeyValue: local project path
},
remotes: {
// name@URL/fileName
SHELL: 'SHELL@http://localhost:3000/remoteEntry.js',
},
shared: [
{
...deps,
react: { requiredVersion: deps.react, singleton: true, eager: true},
'react-dom': {
requiredVersion: deps['react-dom'],
singleton: true,
},
},
],
}
),
  • Shell projesinde tanımlayacağımız modüle federation plugininde ise farklı olarak;
  • remotes: Export edilen micro frontendlerin tanımlandığı yerdir. name@URL/filename şeklinde tanımlanmalıdır.

Import etme standardı -> import(‘name/exposesJsonKey’)

// shell-app/ webpack.config.js
plugins: [
new ModuleFederationPlugin(
{
name: 'SHELL',
filename: 'remoteEntry.js',
remotes: {
// name@URL/fileName
SEARCH: 'SEARCH@http://localhost:3001/remoteEntry.js',
LIST: 'LIST@http://localhost:3003/remoteEntry.js'
},
exposes: {
'./Test': './src/App',
},
shared: [
{
...deps,
react: { requiredVersion: deps.react, singleton: true, eager: true },
'react-dom': {
requiredVersion: deps['react-dom'],
singleton: true,
}
},
],
}
),

Not: Federation yöntemi ile bir proje component export ederken aynı zamanda başka projelerden import da alabilmektedir. Shell projesi search ve list projelerini import edip, App component’ini export etmiştir.

Webpack.config.js dosyalarını ayarladıktan sonra aşağıdaki komut ile projeleri başlatabiliriz.

webpack server

Tarayıcımızdan shell app’e erişmek için localhost:3000 adresine gitmeliyiz. Not: 3000 portunu shell projesinde config içerisine tanımlamıştık

ShellApp

Shell app içerisinde kırmızı ile işaretlenmiş alanlar shell projesinden render edilerken, turuncu alan search projesinden, yeşil alan ise list projesinden alınarak render edilmiştir.

ListApp

List projesi ise 3003 portunda render edilmektedir.

SearchApp

Search projeside kendi domainin de 3001 portunda render edilmektedir.

Sonuç Olarak,

Birbirinden bağımsız olarak geliştirilen projeler gün sonunda tek bir url’de birleşip render edilmiştir.

Her takım kendi domain özelinde geliştirme yapıp, yapılan işe daha iyi odaklanabilmiştir.

Proje kodları daha küçük olduğundan, projeler daha kolay scale edilebilmiş ve test maliyeti düşmüştür.

Geliştirilen uygulamalarda React kullanılmıştır. Fakat farklı teknolojiler ilede projeler geliştirilebilmek mümkün olmuştur.

Gün sonunda projelerde CSS çakışması, kod tekrarları, CSS uyumsuzlukları gibi problemler yaşanmasa da domainler kendi içinde büyüdükçe bu tip sıkıntıların yaşanma ihtimali de artmaktadır.

Sonuç olarak Micro Frontend ile beraber bir çok kolaylık gelirken, bir takım sıkıntılar da yaşanabilmektedir.

Burada düşünülmesi gereken Micro Frontend’in avantajlarını kullanabileceğiniz proje, ekip ve sunucu yapısına sahip misiniz? Sizin projeniz için ne kadar gerekli? Bu sorulara cevap bulup, Micro Frontend mimarisini uygulayabilirsiniz.

--

--