Elixir ve OTP'de Süreç Odaklı Programlama Rehberi
Yayınlanan: 2022-03-11İnsanlar programlama dillerini paradigmalara göre sınıflandırmayı severler. Nesne yönelimli (OO) diller, zorunlu diller, işlevsel diller vb. vardır. Bu, hangi dillerin benzer sorunları çözdüğünü ve bir dilin ne tür sorunları çözmeyi amaçladığını bulmada yardımcı olabilir.
Her durumda, bir paradigmanın genellikle bir "ana" odak ve o dil ailesi için itici güç olan tekniğe sahiptir:
OO dillerinde, durumu (veri) bu durumun (yöntemler) manipülasyonuyla kapsüllemenin bir yolu olarak sınıf veya nesnedir .
İşlevsel dillerde, işlevlerin kendilerinin manipülasyonu veya işlevden işleve aktarılan değişmez veriler olabilir.
İksir (ve ondan önceki Erlang) genellikle işlevsel diller için ortak olan değişmez verileri sergiledikleri için işlevsel diller olarak kategorize edilirken, bunların birçok işlevsel dilden ayrı bir paradigmayı temsil ettiğini söyleyebilirim. OTP'nin varlığından dolayı varlar ve kabul edildiler ve bu yüzden onları süreç odaklı diller olarak sınıflandırırdım.
Bu yazıda, bu dilleri kullanırken süreç yönelimli programlamanın ne anlama geldiğini yakalayacak, diğer paradigmalarla arasındaki farklılıkları ve benzerlikleri keşfedecek, hem eğitim hem de benimseme için etkilerini göreceğiz ve kısa bir süreç yönelimli programlama örneği ile bitireceğiz.
Süreç Odaklı Programlama Nedir?
Bir tanımla başlayalım: Süreç yönelimli programlama , orijinal olarak 1977'de Tony Hoare tarafından yazılan bir makaleden, Sıralı Süreçleri İletmeye dayalı bir paradigmadır. Bu aynı zamanda popüler olarak eşzamanlılık aktör modeli olarak da adlandırılır. Bu orijinal eserle bir ilgisi olan diğer diller arasında Occam, Limbo ve Go bulunmaktadır. Resmi belge yalnızca eşzamanlı iletişimle ilgilenir; çoğu aktör modeli (OTP dahil) asenkron iletişimi de kullanır. Asenkron iletişimin üzerine senkron iletişim kurmak her zaman mümkündür ve OTP her iki formu da destekler.
Bu tarihte OTP, sıralı süreçleri ileterek hataya dayanıklı bilgi işlem için bir sistem yarattı. Hataya dayanıklı tesisler, denetçiler şeklinde katı hata kurtarma ve aktör modeli tarafından etkinleştirilen dağıtılmış işleme kullanımı ile "başarısız olmasına izin ver" yaklaşımından gelir. "Başarısız olmasına izin ver", "başarısız olmasını engelle" ile karşılaştırılabilir, çünkü birincisine uyum sağlamak çok daha kolaydır ve OTP'de ikincisinden çok daha güvenilir olduğu kanıtlanmıştır. Bunun nedeni, arızaları önlemek için gereken programlama çabasının (Java kontrollü istisna modelinde gösterildiği gibi) çok daha karmaşık ve zahmetli olmasıdır.
Bu nedenle, süreç odaklı programlama , süreç yapısının ve bir sistemin süreçleri arasındaki iletişimin birincil kaygılar olduğu bir paradigma olarak tanımlanabilir.
Nesneye Yönelik ve Süreç Yönelimli Programlama
Nesne yönelimli programlamada, verilerin ve işlevin statik yapısı birincil endişe kaynağıdır. Ekteki verileri işlemek için hangi yöntemler gereklidir ve nesneler veya sınıflar arasındaki bağlantılar ne olmalıdır. Bu nedenle, UML'nin sınıf diyagramı, Şekil 1'de görüldüğü gibi, bu odağın başlıca örneğidir.
Nesne yönelimli programlamaya yönelik yaygın bir eleştirinin, görünür bir kontrol akışının olmaması olduğu belirtilebilir. Sistemler ayrı ayrı tanımlanmış çok sayıda sınıf/nesneden oluştuğu için, daha az deneyimli bir kişinin bir sistemin kontrol akışını görselleştirmesi zor olabilir. Bu, özellikle soyut arabirimler kullanan veya güçlü bir yazma özelliği olmayan çok sayıda mirasa sahip sistemler için geçerlidir. Çoğu durumda, geliştiricinin etkili olması için sistem yapısının büyük bir miktarını ezberlemesi önemli hale gelir (hangi sınıfların hangi yöntemlere sahip olduğu ve hangilerinin hangi yollarla kullanıldığı).
Nesne yönelimli geliştirme yaklaşımının gücü, yeni nesne türleri mevcut kodun beklentilerine uyduğu sürece, sistemin mevcut kod üzerinde sınırlı etkisi olan yeni nesne türlerini destekleyecek şekilde genişletilebilmesidir.
İşlevsel ve Süreç Odaklı Programlama
Pek çok işlevsel programlama dili, eşzamanlılığı çeşitli şekillerde ele alır, ancak bunların birincil odak noktası, işlevler arasında geçiş yapan değişmez veriler veya diğer işlevlerden işlevlerin oluşturulmasıdır (işlevleri oluşturan daha yüksek dereceli işlevler). Çoğunlukla, dilin odak noktası hala tek bir adres alanı veya yürütülebilir dosyadır ve bu tür yürütülebilir dosyalar arasındaki iletişim, işletim sistemine özgü bir şekilde işlenir.
Örneğin Scala, Java Sanal Makinesi üzerine kurulmuş işlevsel bir dildir. İletişim için Java olanaklarına erişebilse de, dilin doğal bir parçası değildir. Spark programlamada kullanılan ortak bir dil olsa da yine dil ile birlikte kullanılan bir kütüphanedir.
İşlevsel paradigmanın bir gücü, üst düzey işlev verilen bir sistemin kontrol akışını görselleştirme yeteneğidir. Kontrol akışı, her bir işlevin diğer işlevleri çağırması ve tüm verileri birinden diğerine geçirmesi bakımından açıktır. Fonksiyonel paradigmada, problem belirlemeyi kolaylaştıran hiçbir yan etki yoktur. Saf işlevsel sistemlerle ilgili zorluk, kalıcı duruma sahip olmak için "yan etkilerin" gerekli olmasıdır. İyi mimariye sahip sistemlerde, durumun sürekliliği, kontrol akışının en üst seviyesinde ele alınır ve sistemin çoğunun yan etkiden bağımsız olmasına izin verir.
İksir/OTP ve Süreç Odaklı Programlama
Elixir/Erlang ve OTP'de, iletişim temel öğeleri, dili yürüten sanal makinenin bir parçasıdır. Süreçler arasında ve makineler arasında iletişim kurma yeteneği, dil sisteminin içinde ve merkezinde yer alır. Bu, bu paradigmada ve bu dil sistemlerinde iletişimin önemini vurgular.
Elixir dili, dilde ifade edilen mantık açısından ağırlıklı olarak işlevsel olmakla birlikte, kullanımı süreç odaklıdır .
Süreç Odaklı Olmak Ne Demektir?
Bu yazıda tanımlandığı gibi süreç odaklı olmak, öncelikle hangi süreçlerin var olduğu ve nasıl iletişim kurdukları şeklinde bir sistem tasarlamaktır. Temel sorulardan biri, hangi süreçlerin statik, hangilerinin dinamik olduğu, hangilerinin talepler üzerine ortaya çıktığı, uzun süreli bir amaca hizmet eden, sistemin ortak durumunu veya paylaşılan durumunun bir kısmını elinde tutan ve hangi özelliklerin ortak olduğudur. sistem doğası gereği eşzamanlıdır. OO'nun nesne türleri olması ve işlevselin işlev türleri olması gibi, süreç yönelimli programlamanın da süreç türleri vardır.
Bu nedenle, süreç odaklı tasarım, bir sorunu çözmek veya bir ihtiyaca hitap etmek için gereken süreç türleri kümesinin tanımlanmasıdır .
Zamanın yönü, tasarım ve gereksinim çabalarına hızla girer. Sistemin yaşam döngüsü nedir? Hangi özel ihtiyaçlar ara sıra ve hangileri sabittir? Sistemdeki yük nerede ve beklenen hız ve hacim nedir? Ancak bu tür düşünceler anlaşıldıktan sonra, süreç odaklı bir tasarım, her bir sürecin işlevini veya yürütülecek mantığı tanımlamaya başlar.
Eğitim Etkileri
Bu sınıflandırmanın eğitime etkisi, eğitimin dil sözdizimi veya “Merhaba Dünya” örnekleriyle değil, sistem mühendisliği düşüncesi ve süreç tahsisine odaklanan bir tasarımla başlaması gerektiğidir.
Kodlama endişeleri, en iyi şekilde daha yüksek bir düzeyde ele alınan süreç tasarımı ve tahsisine ikincildir ve yaşam döngüsü, kalite güvencesi, DevOps ve müşteri iş gereksinimleri hakkında işlevler arası düşünmeyi içerir. Elixir veya Erlang'daki herhangi bir eğitim kursu OTP'yi içermelidir (ve genellikle içerir) ve “Şimdi Elixir'de kod yazabilirsiniz, bu yüzden eşzamanlılık yapalım” tipi yaklaşım olarak değil, baştan bir süreç yönelimine sahip olmalıdır.
Evlat Edinme Etkileri
Benimseme, dil ve sistemin iletişim ve/veya bilgi işlem dağıtımını gerektiren sorunlara daha iyi uygulanması anlamına gelir. Tek bir bilgisayarda tek iş yükü olan sorunlar bu alanda daha az ilgi çekicidir ve başka bir dille daha iyi ele alınabilir. Uzun ömürlü sürekli işleme sistemleri, sıfırdan yerleşik hata toleransına sahip olduğundan, bu dil için birincil hedeftir.
Belgeleme ve tasarım çalışmaları için grafiksel bir gösterim kullanmak çok yardımcı olabilir (OO dilleri için şekil 1 gibi). UML'den Elixir ve süreç odaklı programlama için öneri, süreçler arasındaki zamansal ilişkileri göstermek ve bir isteğe hizmet vermede hangi süreçlerin yer aldığını belirlemek için sıra diyagramı (örnek 2'de) olacaktır. Yaşam döngüsü ve süreç yapısını yakalamak için bir UML diyagramı türü yoktur, ancak süreç türleri ve bunların ilişkileri için basit bir kutu ve ok diyagramı ile gösterilebilir. Örneğin, Şekil 3:
Proses Oryantasyonu Örneği
Son olarak, bir probleme süreç oryantasyonunu uygulamanın kısa bir örneğini inceleyeceğiz. Diyelim ki küresel seçimleri destekleyen bir sistem sağlamakla görevlendirildik. Bu problem, birçok bireysel aktivitenin patlamalar halinde gerçekleştirildiği, ancak sonuçların toplanması veya özetlenmesinin gerçek zamanlı olarak istendiği ve önemli bir yük görebileceği için seçilmiştir.

İlk Proses Tasarımı ve Tahsisi
Başlangıçta, her bir birey tarafından oy verilmesinin, birçok ayrık girdiden sisteme bir trafik patlaması olduğunu, zamana göre düzenlenmediğini ve yüksek yüke sahip olabileceğini görebiliriz. Bu faaliyeti desteklemek için, çok sayıda işlemin tümü bu girdileri toplayıp bunları tablolama için daha merkezi bir işleme iletmek isteriz. Bu süreçler, her ülkede oy üretecek nüfusların yakınında yer alabilir ve böylece düşük gecikme süresi sağlayabilir. Yerel sonuçları saklar, girdilerini hemen günlüğe kaydeder ve bant genişliğini ve ek yükü azaltmak için bunları toplu olarak tablolama için iletirler.
Başlangıçta, sonuçların sunulması gereken her yetki alanında oyları izleyen süreçlerin olması gerekeceğini görebiliriz. Bu örnek için, her ülke için ve her ülke içinde il/eyalet bazında sonuçları izlememiz gerektiğini varsayalım. Bu faaliyeti desteklemek için, hesaplamayı gerçekleştiren ve mevcut toplamları koruyan ülke başına en az bir süreç ve her ülkedeki her eyalet/il için başka bir set isteriz. Bu, ülke ve eyalet/il için toplamları gerçek zamanlı veya düşük gecikmeyle yanıtlayabilmemiz gerektiğini varsayar. Sonuçlar bir veritabanı sisteminden elde edilebiliyorsa, toplamların geçici süreçler tarafından güncellendiği farklı bir süreç tahsisi seçebiliriz. Bu hesaplamalar için özel işlemler kullanmanın avantajı, sonuçların bellek hızında gerçekleşmesi ve düşük gecikme ile elde edilebilmesidir.
Son olarak, çok ve çok sayıda insanın sonuçları görüntüleyeceğini görebiliriz. Bu işlemler birçok şekilde bölümlere ayrılabilir. Her ülkeye o ülkenin sonuçlarından sorumlu süreçleri yerleştirerek yükü dağıtmak isteyebiliriz. İşlemler, hesaplama süreçlerindeki sorgu yükünü azaltmak için hesaplama işlemlerinden elde edilen sonuçları önbelleğe alabilir ve/veya hesaplama işlemleri, sonuçların önemli miktarda değiştiği durumlarda veya belirli aralıklarla sonuçlarını uygun sonuç süreçlerine yönlendirebilir. hesaplama sürecinin boşta kalması, değişim hızının yavaşladığını gösterir.
Her üç süreç türünde de süreçleri birbirinden bağımsız olarak ölçeklendirebilir, coğrafi olarak dağıtabilir ve süreçler arası veri aktarımlarının aktif olarak kabul edilmesiyle sonuçların asla kaybolmamasını sağlayabiliriz.
Tartışıldığı gibi, örneğe her süreçte iş mantığından bağımsız bir süreç tasarımı ile başladık. İş mantığının, süreç tahsisini yinelemeli olarak etkileyebilecek veri toplama veya coğrafya için özel gereksinimleri olduğu durumlarda. Şimdiye kadarki süreç tasarımımız şekil 4'te gösterilmektedir.
Oyları almak için ayrı süreçlerin kullanılması, her oylamanın diğer oylardan bağımsız olarak alınmasına, alındıktan sonra günlüğe kaydedilmesine ve bir sonraki süreç grubuna gruplandırılmasına olanak vererek bu sistemler üzerindeki yükü önemli ölçüde azaltır. Büyük miktarda veri tüketen bir sistem için, işlem katmanlarını kullanarak veri hacmini azaltmak yaygın ve kullanışlı bir modeldir.
Hesaplamayı yalıtılmış bir süreç kümesinde gerçekleştirerek, bu süreçler üzerindeki yükü yönetebilir ve kararlılıklarını ve kaynak gereksinimlerini sağlayabiliriz.
Sonuç sunumunu yalıtılmış bir süreç kümesine yerleştirerek, hem sistemin geri kalanına olan yükü kontrol ediyoruz hem de süreç kümesinin yük için dinamik olarak ölçeklendirilmesine izin veriyoruz.
Ek gereksinimler
Şimdi, bazı karmaşık gereksinimler ekleyelim. Her yargı yetkisinde (ülke veya eyalet), oyların sıralanmasının orantılı bir sonuçla, kazanan her şeyi alır sonucuyla sonuçlanabileceğini veya o yargı yetkisinin nüfusuna göre yetersiz oy kullanılması durumunda sonuç alınamayacağını varsayalım. Her yargı yetkisi bu yönler üzerinde kontrole sahiptir. Bu değişiklikle, o zaman ülkelerin sonuçları, ham oy sonuçlarının basit bir toplamı değil, eyalet/il sonuçlarının bir toplamıdır. Bu, süreç tahsisini orijinalden eyalet/il süreçlerinden elde edilen sonuçların ülke süreçlerini beslemesini gerektirecek şekilde değiştirir. Oy toplama ile eyalet/il ve ilden ülkeye süreçleri arasında kullanılan protokol aynıysa, toplama mantığı yeniden kullanılabilir, ancak sonuçları tutan farklı süreçlere ihtiyaç duyulur ve Şekil'de gösterildiği gibi iletişim yolları farklıdır. 5.
kod
Örneği tamamlamak için, örneğin Elixir OTP'deki bir uygulamasını inceleyeceğiz. İşleri basitleştirmek için, bu örnek, gerçek web isteklerini işlemek için Phoenix gibi bir web sunucusunun kullanıldığını ve bu web hizmetlerinin yukarıda tanımlanan sürece istekte bulunduğunu varsayar. Bu, örneği basitleştirme ve Elixir/OTP'ye odaklanma avantajına sahiptir. Bir üretim sisteminde, bunların ayrı süreçler olması, endişeleri ayırmanın yanı sıra bazı avantajlara sahiptir, esnek dağıtıma izin verir, yükü dağıtır ve gecikmeyi azaltır. Testlerle birlikte tam kaynak kodu https://github.com/technomage/voting adresinde bulunabilir. Kaynak, okunabilirlik için bu yazıda kısaltılmıştır. Aşağıdaki her süreç, süreçlerin arıza durumunda yeniden başlatılmasını sağlamak için bir OTP denetim ağacına uyar. Örneğin bu yönü hakkında daha fazla bilgi için kaynağa bakın.
Oy Kaydedici
Bu süreç oyları alır, bunları kalıcı bir depoya kaydeder ve sonuçları toplayıcılara toplu olarak verir. VoteRecoder modülü, her oyu kaydetmek için kısa ömürlü görevleri yönetmek için Task.Supervisor'ı kullanır.
defmodule Voting.VoteRecorder do @moduledoc """ This module receives votes and sends them to the proper aggregator. This module uses supervised tasks to ensure that any failure is recovered from and the vote is not lost. """ @doc """ Start a task to track the submittal of a vote to an aggregator. This is a supervised task to ensure completion. """ def cast_vote where, who do Task.Supervisor.async_nolink(Voting.VoteTaskSupervisor, fn -> Voting.Aggregator.submit_vote where, who end) |> Task.await end end
Oy Toplayıcı
Bu süreç, bir yargı yetkisi dahilindeki oyları toplar, o yargı yetkisi için sonucu hesaplar ve oy özetlerini bir sonraki daha yüksek sürece (daha yüksek bir yargı yetkisi veya sonuç sunucusu) iletir.
defmodule Voting.Aggregator do use GenStage ... @doc """ Submit a single vote to an aggregator """ def submit_vote id, candidate do pid = __MODULE__.via_tuple(id) :ok = GenStage.call pid, {:submit_vote, candidate} end @doc """ Respond to requests """ def handle_call {:submit_vote, candidate}, _from, state do n = state.votes[candidate] || 0 state = %{state | votes: Map.put(state.votes, candidate, n+1)} {:reply, :ok, [%{state.id => state.votes}], state} end @doc """ Handle events from subordinate aggregators """ def handle_events events, _from, state do votes = Enum.reduce events, state.votes, fn e, votes -> Enum.reduce e, votes, fn {k,v}, votes -> Map.put(votes, k, v) # replace any entries for subordinates end end # Any jurisdiction specific policy would go here # Sum the votes by candidate for the published event merged = Enum.reduce votes, %{}, fn {j, jv}, votes -> # Each jourisdiction is summed for each candidate Enum.reduce jv, votes, fn {candidate, tot}, votes -> Logger.debug "@@@@ Votes in #{inspect j} for #{inspect candidate}: #{inspect tot}" n = votes[candidate] || 0 Map.put(votes, candidate, n + tot) end end # Return the published event and the state which retains # Votes by jourisdiction {:noreply, [%{state.id => merged}], %{state | votes: votes}} end end
Sonuç Sunucusu
Bu süreç, bir toplayıcıdan oy alır ve bu sonuçları, sonuçların sunulması için hizmet isteklerine önbelleğe alır.
defmodule Voting.ResultPresenter do use GenStage … @doc """ Handle requests for results """ def handle_call :get_votes, _from, state do {:reply, {:ok, state.votes}, [], state} end @doc """ Obtain the results from this presenter """ def get_votes id do pid = Voting.ResultPresenter.via_tuple(id) {:ok, votes} = GenStage.call pid, :get_votes votes end @doc """ Receive votes from aggregator """ def handle_events events, _from, state do Logger.debug "@@@@ Presenter received: #{inspect events}" votes = Enum.reduce events, state.votes, fn v, votes -> Enum.reduce v, votes, fn {k,v}, votes -> Map.put(votes, k, v) end end {:noreply, [], %{state | votes: votes}} end end
Götürmek
Bu gönderi, Elixir/OTP'yi süreç yönelimli bir dil olarak potansiyelinden araştırdı, bunu nesne yönelimli ve işlevsel paradigmalarla karşılaştırdı ve bunun eğitim ve benimseme üzerindeki etkilerini gözden geçirdi.
Gönderi ayrıca, bu yönelimi örnek bir probleme uygulamanın kısa bir örneğini de içerir. Tüm kodu gözden geçirmek isterseniz, burada tekrar GitHub'daki örneğimize bir bağlantı var, böylece onu aramak için geri kaydırmanız gerekmez.
Anahtar paket, sistemleri iletişim süreçlerinden oluşan bir koleksiyon olarak görmektir. Sistemi önce süreç tasarımı açısından planlayın ve ikinci olarak mantık kodlaması açısından planlayın.