Fungsi entitas

Fungsi entitas mendefinisikan operasi untuk membaca dan memperbarui potongan-potongan kecil status, yang dikenal sebagai entitas tahan lama. Seperti fungsi orkestrator, fungsi entitas adalah fungsi dengan jenis pemicu khusus, pemicu entitas. Tidak seperti fungsi orkestrator, fungsi entitas mengelola status entitas secara eksplisit, daripada secara implisit mewakili status melalui alur kontrol. Entitas menyediakan sarana untuk meningkatkan skala aplikasi dengan mendistribusikan pekerjaan di banyak entitas, masing-masing dengan status berukuran sederhana.

Catatan

Fungsi entitas dan fungsionalitas terkait hanya tersedia dalam Functions Tahan Lama 2.0 ke atas. Mereka saat ini didukung di .NET in-proc, pekerja terisolasi .NET, JavaScript, dan Python, tetapi tidak di PowerShell atau Java.

Penting

Fungsi entitas saat ini tidak didukung di PowerShell dan Java.

Konsep umum

Entitas berperilaku sedikit seperti layanan kecil yang berkomunikasi melalui pesan. Setiap entitas memiliki identitas unik dan status internal (jika ada). Seperti layanan atau objek, entitas melakukan operasi ketika diminta untuk melakukannya. Ketika operasi dijalankan, hal itu mungkin memperbarui status internal entitas. Entitas mungkin juga memanggil layanan eksternal dan menunggu respons. Entitas berkomunikasi dengan entitas lain, orkestrasi, dan klien menggunakan pesan yang dikirim secara implisit melalui antrean yang dapat diandalkan.

Untuk mencegah konflik, semua operasi pada satu entitas dijamin untuk menjalankan secara serial, yaitu satu demi satu.

Catatan

Ketika entitas dipanggil, entitas memproses payloadnya hingga selesai kemudian menjadwalkan eksekusi baru untuk mengaktifkan setelah input di masa mendatang tiba. Akibatnya, log eksekusi entitas Anda mungkin menunjukkan eksekusi ekstra setelah setiap pemanggilan entitas; ini diharapkan.

ID Entitas

Entitas diakses melalui pengidentifikasi unik, ID entitas. ID entitas hanyalah sepasang untai (karakter) yang secara unik mengidentifikasi instans entitas. ID entitas terdiri dari:

  • Nama entitas, merupakan nama yang mengidentifikasi jenis entitas. Contohnya adalah "Counter". Nama ini harus sesuai dengan nama fungsi entitas yang mengimplementasikan entitas. Nama tidak peka terhadap huruf besar/kecil.
  • Kunci entitas, merupakan untai (karakter) yang secara unik mengidentifikasi entitas di antara semua entitas lain dengan nama yang sama. Contohnya adalah GUID.

Misalnya, Counter fungsi entitas dapat digunakan untuk menjaga skor dalam game online. Setiap instans game memiliki ID entitas yang unik, seperti @Counter@Game1 dan @Counter@Game2. Semua operasi yang menargetkan entitas tertentu memerlukan penentuan ID entitas sebagai parameter.

Operasi entitas

Untuk memanggil operasi pada entitas, tentukan:

  • ID entitas dari entitas target.
  • Nama operasi, merupakan untai (karakter) yang menentukan operasi yang akan dilakukan. Misalnya, Counter entitas dapat mendukung operasi add, get, atau reset.
  • Input operasi, merupakan parameter input opsional untuk operasi. Misalnya, operasi tambah dapat mengambil jumlah bilangan bulat sebagai input.
  • Waktu terjadwal, merupakan parameter opsional untuk menentukan waktu pengiriman operasi. Misalnya, operasi dapat dijadwalkan dengan andal untuk berjalan beberapa hari di masa depan.

Operasi dapat mengembalikan nilai hasil atau hasil kesalahan, seperti kesalahan JavaScript atau pengecualian .NET. Hasil atau kesalahan ini terjadi dalam orkestrasi yang memanggil operasi.

Operasi entitas juga dapat membuat, membaca, memperbarui, dan menghapus status entitas. Status entitas selalu bertahan dalam penyimpanan.

Tentukan entitas

Anda menentukan entitas menggunakan sintaks berbasis fungsi, di mana entitas diwakili sebagai fungsi dan operasi secara eksplisit dikirim oleh aplikasi.

Saat ini, ada dua API berbeda untuk menentukan entitas di .NET:

Saat Anda menggunakan sintaks berbasis fungsi, entitas direpresentasikan sebagai fungsi dan operasi secara eksplisit dikirim oleh aplikasi. Sintaks ini bekerja dengan baik untuk entitas dengan status sederhana, beberapa operasi, atau serangkaian operasi dinamis seperti dalam kerangka kerja aplikasi. Sintaks ini bisa membosankan untuk dipertahankan karena tidak menangkap kesalahan jenis pada waktu kompilasi.

API tertentu bergantung pada apakah fungsi C# Anda berjalan dalam proses pekerja terisolasi (disarankan) atau dalam proses yang sama dengan host.

Kode berikut adalah contoh entitas sederhana Counter yang diimplementasikan sebagai fungsi tahan lama. Fungsi ini mendefinisikan tiga operasi, add, reset, dan get, masing-masing beroperasi pada status bilangan bulat.

[FunctionName("Counter")]
public static void Counter([EntityTrigger] IDurableEntityContext ctx)
{
    switch (ctx.OperationName.ToLowerInvariant())
    {
        case "add":
            ctx.SetState(ctx.GetState<int>() + ctx.GetInput<int>());
            break;
        case "reset":
            ctx.SetState(0);
            break;
        case "get":
            ctx.Return(ctx.GetState<int>());
            break;
    }
}

Untuk informasi selengkapnya tentang sintaks berbasis fungsi dan cara menggunakannya, lihat Sintaks berbasis fungsi.

Entitas tahan lama tersedia di JavaScript mulai dari versi 1.3.0 paket npm durable-functions. Kode berikut adalah entitas Counter yang diimplementasikan sebagai fungsi tahan lama yang ditulis dalam JavaScript.

Counter/function.json

{
  "bindings": [
    {
      "name": "context",
      "type": "entityTrigger",
      "direction": "in"
    }
  ],
  "disabled": false
}

Counter/index.js

const df = require("durable-functions");

module.exports = df.entity(function(context) {
    const currentValue = context.df.getState(() => 0);
    switch (context.df.operationName) {
        case "add":
            const amount = context.df.getInput();
            context.df.setState(currentValue + amount);
            break;
        case "reset":
            context.df.setState(0);
            break;
        case "get":
            context.df.return(currentValue);
            break;
    }
});

Catatan

Lihat panduan pengembang Azure Functions Python untuk detail selengkapnya tentang cara kerja model V2.

Kode berikut adalah entitas Counter yang diimplementasikan sebagai fungsi tahan lama yang ditulis dalam Python.

import azure.functions as func
import azure.durable_functions as df

# Entity function called counter
@myApp.entity_trigger(context_name="context")
def Counter(context):
    current_value = context.get_state(lambda: 0)
    operation = context.operation_name
    if operation == "add":
        amount = context.get_input()
        current_value += amount
    elif operation == "reset":
        current_value = 0
    elif operation == "get":
        context.set_result(current_value)
    context.set_state(current_value)

Mengakses entitas

Entitas dapat diakses menggunakan komunikasi satu atau dua arah. Terminologi berikut membedakan dua bentuk komunikasi:

  • Memanggil entitas menggunakan komunikasi dua arah (pulang-pergi). Anda mengirim pesan operasi ke entitas, lalu menunggu pesan respons sebelum melanjutkan. Pesan respons dapat memberikan nilai hasil atau hasil kesalahan, seperti kesalahan JavaScript atau pengecualian .NET. Kemudian, hasil atau kesalahan ini diamati oleh penelepon.
  • Pemberian sinyal entitas menggunakan komunikasi satu arah (beri sinyal dan lupakan). Anda mengirim pesan operasi tetapi tidak menunggu respons. Meskipun pesan dijamin akan dikirimkan pada akhirnya, pengirim tidak tahu kapan dan tidak dapat mengamati nilai hasil atau kesalahan apa pun.

Entitas dapat diakses dari dalam fungsi klien, orkestrator, atau entitas. Tidak semua bentuk komunikasi didukung oleh semua konteks:

  • Dari dalam klien, Anda dapat memberi sinyal entitas dan membaca status entitas.
  • Dari dalam orkestrasi, Anda dapat memberi sinyal entitas dan memanggil entitas.
  • Dari dalam entitas, Anda dapat memberi sinyal entitas.

Contoh-contoh berikut menggambarkan berbagai cara mengakses entitas ini.

Contoh: Klien memberi sinyal entitas

Untuk mengakses entitas dari Azure Functions biasa, yang juga dikenal sebagai fungsi klien, gunakan pengikatan klien entitas. Contoh berikut menunjukkan fungsi yang dipicu antrean yang menandakan entitas menggunakan pengikatan ini.

Catatan

Sederhananya, contoh berikut menunjukkan sintaks yang diketik secara mudah untuk mengakses entitas. Secara umum, kami sarankan Anda mengakses entitas melalui antarmuka karena menyediakan lebih banyak pemeriksaan jenis.

[FunctionName("AddFromQueue")]
public static Task Run(
    [QueueTrigger("durable-function-trigger")] string input,
    [DurableClient] IDurableEntityClient client)
{
    // Entity operation input comes from the queue message content.
    var entityId = new EntityId(nameof(Counter), "myCounter");
    int amount = int.Parse(input);
    return client.SignalEntityAsync(entityId, "Add", amount);
}
const df = require("durable-functions");

module.exports = async function (context) {
    const client = df.getClient(context);
    const entityId = new df.EntityId("Counter", "myCounter");
    await client.signalEntity(entityId, "add", 1);
};
import azure.functions as func
import azure.durable_functions as df

# An HTTP-Triggered Function with a Durable Functions Client to set a value on a durable entity
@myApp.route(route="entitysetvalue")
@myApp.durable_client_input(client_name="client")
async def http_set(req: func.HttpRequest, client):
    logging.info('Python HTTP trigger function processing a request.')
    entityId = df.EntityId("Counter", "myCounter")
    await client.signal_entity(entityId, "add", 1)
    return func.HttpResponse("Done", status_code=200)

Istilah sinyal berarti pemanggilan API entitas yaitu satu arah dan asinkron. Tidak dimungkinkan bagi fungsi klien untuk mengetahui kapan entitas telah memproses operasi. Selain itu, fungsi klien tidak bisa mengamati nilai hasil atau pengecualian apa pun.

Contoh: Klien membaca status entitas

Fungsi klien juga dapat mengkueri status entitas, seperti yang diperlihatkan dalam contoh berikut:

[FunctionName("QueryCounter")]
public static async Task<HttpResponseMessage> Run(
    [HttpTrigger(AuthorizationLevel.Function)] HttpRequestMessage req,
    [DurableClient] IDurableEntityClient client)
{
    var entityId = new EntityId(nameof(Counter), "myCounter");
    EntityStateResponse<JObject> stateResponse = await client.ReadEntityStateAsync<JObject>(entityId);
    return req.CreateResponse(HttpStatusCode.OK, stateResponse.EntityState);
}
const df = require("durable-functions");

module.exports = async function (context) {
    const client = df.getClient(context);
    const entityId = new df.EntityId("Counter", "myCounter");
    const stateResponse = await client.readEntityState(entityId);
    return stateResponse.entityState;
};
# An HTTP-Triggered Function with a Durable Functions Client to retrieve the state of a durable entity
@myApp.route(route="entityreadvalue")
@myApp.durable_client_input(client_name="client")
async def http_read(req: func.HttpRequest, client):
    entityId = df.EntityId("Counter", "myCounter")
    entity_state_result = await client.read_entity_state(entityId)
    entity_state = "No state found"
    if entity_state_result.entity_exists:
      entity_state = str(entity_state_result.entity_state)
    return func.HttpResponse(entity_state)

Kueri status entitas dikirim ke penyimpanan pelacakan Tahan lama dan mengembalikan status entitas yang terakhir bertahan. Status ini selalu menjadi status "committed", yaitu, tidak pernah menjadi status perantara sementara yang diasumsikan di tengah-tengah pelaksanaan operasi. Namun, ada kemungkinan bahwa keadaan ini kedaluwarsa dibandingkan dengan keadaan dalam memori entitas. Hanya orkestrasi yang dapat membaca status dalam memori entitas, seperti yang dijelaskan di bagian berikut.

Contoh: Orkestrasi memberi sinyal dan memanggil entitas

Fungsi orkestrator dapat mengakses entitas dengan menggunakan API pada pengikatan pemicu orkestrasi. Contoh kode berikut menunjukkan fungsi orkestrator memanggil dan menandakan entitas Counter.

[FunctionName("CounterOrchestration")]
public static async Task Run(
    [OrchestrationTrigger] IDurableOrchestrationContext context)
{
    var entityId = new EntityId(nameof(Counter), "myCounter");

    // Two-way call to the entity which returns a value - awaits the response
    int currentValue = await context.CallEntityAsync<int>(entityId, "Get");
    if (currentValue < 10)
    {
        // One-way signal to the entity which updates the value - does not await a response
        context.SignalEntity(entityId, "Add", 1);
    }
}
const df = require("durable-functions");

module.exports = df.orchestrator(function*(context){
    const entityId = new df.EntityId("Counter", "myCounter");

    // Two-way call to the entity which returns a value - awaits the response
    currentValue = yield context.df.callEntity(entityId, "get");
});

Catatan

JavaScript saat ini tidak mendukung sinyal entitas dari orkestrator. Gunakan callEntity sebagai gantinya.

@myApp.orchestration_trigger(context_name="context")
def orchestrator(context: df.DurableOrchestrationContext):
    entityId = df.EntityId("Counter", "myCounter")
    context.signal_entity(entityId, "add", 3)
    logging.info("signaled entity")
    state = yield context.call_entity(entityId, "get")
    return state

Hanya orkestrasi yang mampu memanggil entitas dan mendapatkan respons, yang bisa berupa nilai pengembalian atau pengecualian. Fungsi klien yang menggunakan pengikatan klien hanya dapat memberi sinyal entitas.

Catatan

Memanggil entitas dari fungsi orkestrator mirip dengan memanggil fungsi aktivitas dari fungsi orkestrator. Perbedaan utama adalah bahwa fungsi entitas adalah objek tahan lama dengan alamat, yang merupakan ID entitas. Fungsi entitas mendukung penentuan nama operasi. Fungsi aktivitas, di sisi lain, tanpa status dan tidak memiliki konsep operasi.

Contoh: Entitas memberi sinyal entitas

Fungsi entitas dapat mengirim sinyal ke entitas lain, atau bahkan diri sendiri, saat menjalankan operasi. Misalnya, kita dapat memodifikasi contoh entitas Counter sebelumnya sehingga mengirimkan sinyal "milestone-reached" ke beberapa entitas monitor ketika penghitung mencapai nilai 100.

   case "add":
        var currentValue = ctx.GetState<int>();
        var amount = ctx.GetInput<int>();
        if (currentValue < 100 && currentValue + amount >= 100)
        {
            ctx.SignalEntity(new EntityId("MonitorEntity", ""), "milestone-reached", ctx.EntityKey);
        }

        ctx.SetState(currentValue + amount);
        break;
    case "add":
        const amount = context.df.getInput();
        if (currentValue < 100 && currentValue + amount >= 100) {
            const entityId = new df.EntityId("MonitorEntity", "");
            context.df.signalEntity(entityId, "milestone-reached", context.df.instanceId);
        }
        context.df.setState(currentValue + amount);
        break;

Catatan

Python belum mendukung sinyal entitas-ke-entitas. Silakan gunakan orkestrator untuk memberi sinyal entitas sebagai gantinya.

Koordinasi entitas

Mungkin ada saat ketika Anda perlu mengkoordinasikan operasi di beberapa entitas. Misalnya, dalam aplikasi perbankan, Anda mungkin memiliki entitas yang mewakili rekening bank individual. Ketika Anda mentransfer dana dari satu akun ke akun lain, Anda harus memastikan bahwa akun sumber memiliki dana yang cukup. Anda juga harus memastikan pembaruan pada akun sumber dan tujuan dilakukan dengan cara yang konsisten secara transaksional.

Contoh: Mentransfer dana

Contoh kode berikut mentransfer dana antara dua entitas akun dengan menggunakan fungsi orkestrator. Pembaruan entitas koordinasi memerlukan penggunaan metode LockAsync untuk membuat bagian kritis dalam orkestrasi.

Catatan

Sederhananya, contoh ini menggunakan kembali entitas Counter yang ditentukan sebelumnya. Dalam aplikasi nyata, akan lebih baik untuk mendefinisikan entitas BankAccount yang lebih rinci.

// This is a method called by an orchestrator function
public static async Task<bool> TransferFundsAsync(
    string sourceId,
    string destinationId,
    int transferAmount,
    IDurableOrchestrationContext context)
{
    var sourceEntity = new EntityId(nameof(Counter), sourceId);
    var destinationEntity = new EntityId(nameof(Counter), destinationId);

    // Create a critical section to avoid race conditions.
    // No operations can be performed on either the source or
    // destination accounts until the locks are released.
    using (await context.LockAsync(sourceEntity, destinationEntity))
    {
        ICounter sourceProxy = 
            context.CreateEntityProxy<ICounter>(sourceEntity);
        ICounter destinationProxy =
            context.CreateEntityProxy<ICounter>(destinationEntity);

        int sourceBalance = await sourceProxy.Get();

        if (sourceBalance >= transferAmount)
        {
            await sourceProxy.Add(-transferAmount);
            await destinationProxy.Add(transferAmount);

            // the transfer succeeded
            return true;
        }
        else
        {
            // the transfer failed due to insufficient funds
            return false;
        }
    }
}

Di .NET, LockAsync kembalikan IDisposable, yang mengakhiri bagian kritis saat dibuang. Hasil IDisposable dapat digunakan bersama dengan blok using untuk mendapatkan representasi sintaksis dari critical section.

Dalam contoh sebelumnya, fungsi orkestrator mentransfer dana dari entitas sumber ke entitas tujuan. Metode LockAsync ini mengunci entitas akun sumber dan tujuan. Penguncian ini memastikan bahwa tidak ada klien lain yang dapat meminta atau memodifikasi status salah satu akun sampai logika orkestrasi keluar dari bagian kritis di akhir pernyataan using. Perilaku ini mencegah kemungkinan overdrafting dari akun sumber.

Catatan

Ketika orkestrasi berakhir, baik secara normal atau dengan kesalahan, setiap bagian kritis yang sedang berlangsung secara implisit berakhir dan semua kunci dilepaskan.

Perilaku bagian kritis

Metode LockAsync ini membuat bagian kritis dalam orkestrasi. Bagian kritis ini mencegah orkestrasi lain melakukan perubahan tumpang tindih ke sekumpulan entitas tertentu. Secara internal, LockAsync API mengirimkan operasi "kunci" ke entitas dan kembali ketika menerima pesan respons "kunci yang diperoleh" dari masing-masing entitas yang sama ini. Kunci dan buka kunci adalah operasi bawaan yang didukung oleh semua entitas.

Tidak ada operasi dari klien lain yang diizinkan pada entitas saat berada dalam status terkunci. Perilaku ini memastikan bahwa hanya satu instans orkestrasi yang dapat mengunci entitas pada satu waktu. Jika penelepon mencoba memanggil operasi pada entitas saat terkunci oleh orkestrasi, operasi tersebut ditempatkan dalam antrean operasi yang tertunda. Tidak ada operasi yang tertunda diproses sampai setelah orkestrasi penahanan melepaskan kuncinya.

Catatan

Perilaku ini sedikit berbeda dari primitif sinkronisasi yang digunakan dalam sebagian besar bahasa pemrograman, seperti pernyataan lock dalam C#. Misalnya, dalam C#, pernyataan lock harus digunakan oleh semua percakapan untuk memastikan sinkronisasi yang tepat di beberapa percakapan. Entitas, bagaimanapun, tidak mengharuskan semua penelepon untuk secara eksplisit mengunci entitas. Jika ada penelepon yang mengunci entitas, semua operasi lain pada entitas tersebut diblokir dan mengantri di balik kunci tersebut.

Kunci pada entitas bersifat tahan lama, sehingga mereka bertahan bahkan jika proses eksekusi didaur ulang. Kunci secara internal bertahan sebagai bagian dari status tahan lama entitas.

Tidak seperti transaksi, bagian penting tidak secara otomatis mengembalikan perubahan saat kesalahan terjadi. Sebaliknya, setiap penanganan kesalahan, seperti menggulung balik atau coba lagi, harus secara eksplisit dikodekan, misalnya dengan menangkap kesalahan atau pengecualian. Pilihan desain ini disengaja. Secara otomatis menggulung balik semua efek orkestrasi sulit atau tidak mungkin secara umum, karena orkestrasi mungkin menjalankan aktivitas dan melakukan panggilan ke layanan eksternal yang tidak dapat gulung kembali. Selain itu, upaya untuk mundur mungkin gagal dan memerlukan penanganan kesalahan lebih lanjut.

Aturan bagian kritis

Tidak seperti penguncian primitif tingkat rendah dalam sebagian besar bahasa pemrograman, bagian kritis dijamin tidak mengalami kebuntuan. Untuk mencegah kebuntuan, kami memberlakukan pembatasan berikut:

  • Bagian kritis tidak dapat dilapis.
  • Bagian kritis tidak dapat membuat sub orkestrasi.
  • Bagian kritis hanya dapat memanggil entitas yang telah mereka kunci.
  • Bagian kritis tidak dapat memanggil entitas yang sama menggunakan beberapa panggilan paralel.
  • Bagian kritis hanya dapat menandakan entitas yang belum mereka kunci.

Setiap pelanggaran aturan ini menyebabkan kesalahan runtime, seperti LockingRulesViolationException di .NET, yang menyertakan pesan penjelasan aturan yang dilanggar.

Perbandingan dengan aktor virtual

Banyak fitur entitas tahan lama terinspirasi oleh model aktor. Jika Anda sudah terbiasa dengan aktor, Anda mungkin mengenali banyak konsep yang dijelaskan dalam artikel ini. Entitas tahan lama mirip dengan aktor virtual, atau biji-bijian, seperti yang dimopulerkan oleh proyek Orleans. Contohnya:

  • Entitas tahan lama dapat diatasi melalui ID entitas.
  • Operasi entitas tahan lama dijalankan secara serial, satu per satu, untuk mencegah kondisi balapan.
  • Entitas yang tahan lama dibuat secara implisit ketika mereka dipanggil atau diberi sinyal.
  • Entitas tahan lama dibongkar secara diam-diam dari memori saat tidak menjalankan operasi.

Ada beberapa perbedaan penting yang perlu diperhatikan:

  • Entitas yang tahan lama memprioritaskan daya tahan daripada latensi, sehingga mungkin tidak sesuai untuk aplikasi dengan persyaratan latensi yang ketat.
  • Entitas tahan lama tidak memiliki batas waktu bawaan untuk pesan. Di Orleans, semua pesan akan habis setelah waktunya dikonfigurasi. Defaultnya adalah 30 detik.
  • Pesan yang dikirim antar entitas dikirimkan dengan andal dan teratur. Di Orleans, pengiriman yang andal atau dipesan didukung untuk konten yang dikirim melalui streaming, tetapi tidak dijamin untuk semua pesan antar grain.
  • Pola permintaan-respons dalam entitas terbatas pada orkestrasi. Dari dalam entitas, hanya pesan satu arah (juga dikenal sebagai pemberian sinyal) yang diizinkan, seperti dalam model aktor asli, dan tidak seperti grain di Orleans.
  • Entitas tahan lama tidak mengalami kebuntuan. Di Orleans, kebuntuan dapat terjadi dan tidak terselesaikan hingga pesan waktu habis.
  • Entitas tahan lama dapat digunakan dengan orkestrasi tahan lama dan mendukung mekanisme penguncian terdistribusi.

Langkah berikutnya