Paylaşılan verileri bir yazılım hattında kapsüllemek için iyi uygulama stratejileri


13

Varolan bir web hizmetinin belirli yönlerini yeniden faktoring üzerinde çalışıyorum. Hizmet API'lerinin uygulanma şekli, sırayla gerçekleştirilen görevlerin olduğu bir tür "işleme hattı" na sahip olmaktır. Şaşırtıcı olmayan bir şekilde, daha sonraki görevlerin önceki görevler tarafından hesaplanan bilgilere ihtiyacı olabilir ve şu anda bunun gerçekleştirilme şekli bir "ardışık düzen durumu" sınıfına alan eklemektir.

Boru hattı adımları arasında bilgi paylaşmanın, bazıları için bazı işlem adımlarına ve başkalarına değil, bazı işleme adımlarına mantıklı olan bir veri nesnesine sahip olmaktan daha iyi bir yol olduğunu düşündüm (ve umuyorum?). Bu sınıfı iplik güvenli hale getirmek için büyük bir acı olurdu (hatta mümkün olup olmadığını bilmiyorum), onun değişmezleri hakkında bir neden yoktur (ve muhtemelen herhangi bir yok).

Biraz ilham bulmak için Gang of Four tasarım desenleri kitabında dolaşıyordum, ama orada bir çözüm varmış gibi hissetmedim (Memento biraz aynı ruhtaydı, ama tam olarak değil). Ayrıca çevrimiçi baktım, ancak "boru hattı" veya "iş akışı" için arama yaptığınızda, Unix boru bilgileri veya tescilli iş akışı motorları ve çerçeveleri ile sular altında kalabilirsiniz.

Benim sorum - bir yazılım işleme boru hattının yürütme durumunu kaydetme konusuna nasıl yaklaşırsınız, böylece daha sonraki görevler daha önceki görevler tarafından hesaplanan bilgileri kullanabilir? Unix borulardaki en büyük fark, sadece hemen önceki görevin çıktısını umursamamanızdır.


İstendiği gibi, kullanım durumumu göstermek için bazı sözde kod:

"Pipeline bağlamı" nesnesi, farklı pipeline adımlarının doldurabileceği / okuyabileceği bir grup alana sahiptir:

public class PipelineCtx {
    ... // fields
    public Foo getFoo() { return this.foo; }
    public void setFoo(Foo aFoo) { this.foo = aFoo; }
    public Bar getBar() { return this.bar; }
    public void setBar(Bar aBar) { this.bar = aBar; }
    ... // more methods
}

Boru hattı adımlarının her biri de bir nesnedir:

public abstract class PipelineStep {
    public abstract PipelineCtx doWork(PipelineCtx ctx);
}

public class BarStep extends PipelineStep {
    @Override
    public PipelineCtx doWork(PipelieCtx ctx) {
        // do work based on the stuff in ctx
        Bar theBar = ...; // compute it
        ctx.setBar(theBar);

        return ctx;
    }
}

Benzer şekilde FooStep, diğer verilerle birlikte BarStep tarafından hesaplanan Çubuğa ihtiyaç duyabilecek bir varsayım için . Ve sonra gerçek API çağrısı var:

public class BlahOperation extends ProprietaryWebServiceApiBase {
    public BlahResponse handle(BlahRequest request) {
        PipelineCtx ctx = PipelineCtx.from(request);

        // some steps happen here
        // ...

        BarStep barStep = new BarStep();
        barStep.doWork(crx);

        // some more steps maybe
        // ...

        FooStep fooStep = new FooStep();
        fooStep.doWork(ctx);

        // final steps ...

        return BlahResponse.from(ctx);
    }
}

6
yazı çapraz değil ama bir mod taşımak için bayrak
cırcır ucube

1
İlerleyeceğim, sanırım kuralları tanımak için daha fazla zaman harcamalıyım. Teşekkürler!
RuslanD

1
Uygulamanız için kalıcı veri depolamadan kaçınıyor musunuz veya bu noktada kapmak için bir şey var mı?
CokoBWare

1
Merhaba RuslanD ve hoş geldiniz! Bu gerçekten Programcılar için Stack Overflow'dan daha uygundur, bu yüzden SO sürümünü kaldırdık. @Ratchetfreak'in bahsettiğini unutmayın, ılımlı dikkat için işaretleyebilir ve bir soruyu daha uygun bir siteye taşınmasını isteyebilirsiniz, çapraz posta göndermeye gerek yoktur. İki site arasında seçim yapmanın temel kuralı, Programcıların projelerinizi tasarlayan beyaz tahtanın önündeyken karşılaştığınız problemler için olması ve Yığın Taşması daha teknik problemler (örn. Uygulama sorunları) içindir. Daha fazla bilgi için SSS bölümümüze bakın .
yannis

1
Mimariyi bir boru hattı yerine bir işleme DAG (yönlendirilmiş döngüsel grafik) olarak değiştirirseniz, önceki adımların sonuçlarını açıkça geçirebilirsiniz.
Patrick

Yanıtlar:


4

Bir boru hattı tasarımı kullanmanın ana nedeni, aşamaları ayırmak istemenizdir. Bir aşama birden çok boru hattında (Unix kabuk araçları gibi) kullanılabileceği için veya bir miktar ölçeklendirme avantajı elde ettiğiniz için (yani, tek düğümlü bir mimariden çok düğümlü bir mimariye kolayca geçebilirsiniz).

Her iki durumda da, boru hattındaki her aşamaya işini yapmak için ihtiyaç duyduğu her şey verilmelidir. Harici bir mağaza (ör. Veritabanı) kullanamamanız için hiçbir neden yoktur, ancak çoğu durumda verileri bir aşamadan diğerine geçirmek daha iyidir.

Bununla birlikte, bu, olası her alanla birlikte büyük bir mesaj nesnesi geçirmeniz gerektiği veya geçmeniz gerektiği anlamına gelmez (aşağıya bakın). Bunun yerine, boru hattındaki her aşama, yalnızca aşamaya ihtiyaç duyulan verileri tanımlayan giriş ve çıkış iletileri için arabirimler tanımlamalıdır .

Daha sonra gerçek mesaj nesnelerinizi nasıl uyguladığınız konusunda çok fazla esnekliğe sahipsiniz. Bir yaklaşım, gerekli tüm arabirimleri uygulayan büyük bir veri nesnesi kullanmaktır. Başka bir basit bir sarmalayıcı sınıfları oluşturmaktır Map. Başka bir veritabanı etrafında bir sarmalayıcı sınıfı oluşturmaktır.


1

Akla sıçrayan birkaç düşünce var, birincisi yeterli bilgim yok.

  • Her adım boru hattının ötesinde kullanılan veriler üretir mi yoksa yalnızca son aşamanın sonuçlarını mı önemsiyoruz?
  • Birçok büyük veri kaygısı var mı? yani. bellek endişeleri, hız endişeleri, vb.

Cevaplar muhtemelen tasarım hakkında daha dikkatli düşünmemi sağlayacak, ancak söylediklerinize dayanarak muhtemelen ilk olarak düşüneceğim 2 yaklaşım var.

Her aşamayı kendi nesnesi olarak yapılandırın. Dördüncü aşamada delegelerin bir listesi olarak 1'den n-1'e kadar aşamalar olacaktır. Her aşama verileri ve verilerin işlenmesini kapsamaktadır; her bir nesnenin genel karmaşıklığını ve alanlarını azaltmak. Ayrıca, daha sonraki aşamaların gerektiğinde delegeleri gezerek daha önceki aşamalardan verilere erişmesini sağlayabilirsiniz. Tüm nesneler arasında hala oldukça sıkı bir bağlantınız var, çünkü önemli olan aşamaların (yani tüm davetsiz misafirlerin) sonuçları, ancak önemli ölçüde azaldı ve her aşama / nesne muhtemelen daha okunabilir ve anlaşılabilir. Temsilci listesini tembel yaparak ve her nesnede temsilci listesini gerektiği gibi doldurmak için bir iş parçacığı güvenli kuyruğu kullanarak iş parçacığını güvenli hale getirebilirsiniz.

Alternatif olarak muhtemelen yaptığınız işe benzer bir şey yapardım. Her aşamayı temsil eden işlevlerden geçen büyük bir veri nesnesi. Bu genellikle çok daha hızlı ve hafiftir, ancak sadece büyük bir veri özellikleri yığını nedeniyle daha karmaşık ve hataya yatkındır. Açıkçası iplik güvenli değil.

Dürüst olmak gerekirse, daha sonra ETL ve diğer benzer problemler için daha sık yaptım. Sürdürülebilirlik yerine veri miktarı nedeniyle performansa odaklandım. Ayrıca, tekrar kullanılmayacak olan bir kereliklerdi.


1

Bu, GoF'deki bir Zincir Deseni'ne benziyor.

Ortak bir zincirin ne yaptığına bakmak iyi bir başlangıç ​​olabilir .

Karmaşık işleme akışlarının yürütülmesini organize etmek için popüler bir teknik, klasik "Dörtlü Çete" tasarım desenleri kitabında (diğer birçok yerde) açıklandığı gibi "Sorumluluk Zinciri" örüntüsüdür. Bu tasarım pattenini uygulamak için gereken temel API sözleşmeleri son derece basit olsa da, kalıbın kullanımını kolaylaştıran ve (daha da önemlisi) birden çok farklı kaynaktan komut uygulamalarının kompozisyonunu teşvik eden bir temel API'ye sahip olmak yararlıdır.

Bu amaçla, Zincir API bir hesaplamayı bir "zincir" halinde birleştirilebilen bir dizi "komut" olarak modeller. Komut için API execute(), hesaplamanın dinamik durumunu içeren bir "bağlam" parametresinden geçirilen ve dönüş değeri, geçerli zincir için işlemenin tamamlanıp tamamlanmadığını belirleyen bir boole olan tek bir yöntemden ( ) oluşur ( true) veya işlemin zincirdeki bir sonraki komuta (false) aktarılması gerekip gerekmediği.

"Bağlam" soyutlaması, komut uygulamalarını çalıştıkları ortamdan yalıtmak üzere tasarlanmıştır (bu ortamlardan herhangi birinin API sözleşmelerine doğrudan bağlanmadan bir Servlet veya Portlet'te kullanılabilen bir komut gibi). Temsilci seçmeden önce kaynakları ayırması ve sonra dönüşte bunları serbest bırakması gereken komutlar için (devredilen bir komut bir istisna atsa bile), "komut" için "filtre" uzantısı postprocess()bu temizleme için bir yöntem sağlar . Son olarak, hangi komutun (veya zincirin) gerçekte yürütüldüğü kararının ertelenmesine izin vermek için komutlar bir "katalogda" saklanabilir ve aranabilir.

Sorumluluk Zinciri desen API'lerinin kullanışlılığını en üst düzeye çıkarmak için, temel arayüz sözleşmeleri uygun bir JDK dışında sıfır bağımlılığa sahip bir şekilde tanımlanır. Bu API'lerin uygunluk sınıfı uygulamaları, web ortamı (örneğin sunucu uygulamaları ve portletler) için daha özel (ancak isteğe bağlı) uygulamalar sağlanır.

Komut uygulamalarının bu önerilere uyacak şekilde tasarlandığı göz önüne alındığında, Sorumluluk Zinciri API'lerini bir web uygulama çerçevesinin (Struts gibi) "ön denetleyicisi" nde kullanmak mümkün olmalı, ancak bunu işyerinde de kullanabilmelidir. Kompozisyon yoluyla karmaşık hesaplama gereksinimlerini modellemek için mantık ve kalıcılık katmanları. Ek olarak, bir hesaplamanın genel amaçlı bir bağlamda çalışan ayrık komutlara ayrılması, birim test edilebilir komutların daha kolay oluşturulmasına izin verir, çünkü bir komutun yürütülmesinin etkisi, sağlanan bağlamdaki karşılık gelen durum değişikliklerini gözlemleyerek doğrudan ölçülebilir ...


0

Hayal edebileceğim ilk çözüm adımları açık hale getirmektir. Her biri bir veri parçasını işleyebilen ve bir sonraki işlem nesnesine iletebilen bir nesne haline gelir. Her süreç yeni (ideal olarak değişmez) bir ürün üretir, böylece süreçler arasında etkileşim olmaz ve veri paylaşımı nedeniyle risk olmaz. Bazı işlemler diğerlerinden daha fazla zaman alıyorsa, iki işlem arasına bir arabellek yerleştirebilirsiniz. Çoklu iş parçacığı için bir zamanlayıcıdan doğru bir şekilde yararlanırsanız, arabellekleri temizlemek için daha fazla kaynak ayıracaktır.

İkinci bir çözüm, muhtemelen özel bir çerçeveyle, boru hattı yerine "mesaj" düşünmek olabilir. Daha sonra başka aktörlerden mesaj alan ve diğer aktörlere başka mesajlar gönderen bazı "aktörleriniz" var. Aktörlerinizi bir boru hattında düzenlersiniz ve birincil verilerinizi zinciri başlatan ilk aktöre verirsiniz. Paylaşımın yerini mesaj gönderimi aldığı için veri paylaşımı yok. Scala'nın aktör modelinin Java'da kullanılabileceğini biliyorum, çünkü burada Scala'ya özgü bir şey yok, ama asla bir Java programında kullanmadım.

Çözümler benzerdir ve ikincisini ilkiyle uygulayabilirsiniz. Temel olarak, temel kavramlar, veri paylaşımı nedeniyle geleneksel sorunlardan kaçınmak için değişmez verilerle uğraşmak ve boru hattınızdaki süreçleri temsil eden açık ve bağımsız varlıklar oluşturmaktır. Bu koşulları yerine getirirseniz, kolayca net, basit boru hatları oluşturabilir ve bunları paralel bir programda kullanabilirsiniz.


Hey, sorumu bazı sahte kodlarla güncelledim - aslında adımlar açık.
RuslanD
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.