Derinlemesine yazılım eğitimleri için kanalımı takip edebilirsiniz...

.NET’te S3 Event’leriyle AWS Lambda’yı Tetikleyelim

Merhaba,

Bu içeriğimizde, AWS Lambda’yı S3 event’leri eşliğinde nasıl tetikleyebileceğimizi inceliyor olacağız. Tabi bunu yaparken önceden klavyeye almış olduğumuz AWS Lambda İle Serverless Computing ve AWS S3 İle Dosya İşlemleri Gerçekleştirme başlıklı makalelerimizden de istifade edecek ve bunlardan event-driven yaklaşıma sahip bir sistem oluşturmak için nasıl entegrasyonun yapılabileceğini, gerçek hayattan bir senaryo üzerinden değerlendirmede bulunacağız. O halde buyurun başlayalım…

Senaryo

Öncelikle senaryomuzun tahayyülünü gerçekleştirerek başlayalım…

Diyelim ki, bir Asp.NET Core web uygulamamız var ve kullanıcılar tarafından daha önce hiç olmadığı kadar resim yükleme eylemi gerçekleştirilmektedir… Arka planda ise yüklenen her resim için depolama maliyetini optimize edebilmek maksadıyla belli boyutlandırma işlemleri yapılmakta ve bir yerden sonra bu işlemler uygulama açısından bir darboğaz oluşturduğu için, bu resim işlemeyi verimli bir şekilde yönetme zorunluluğu ortaya çıkmaktadır… İşte tam bu noktada bu görev için otomatik olarak ölçeklenen bir Serverless Lambda(AWS Lambda) asenkron olarak devreye girmekte ve yüklenecek resimleri S3’e göndererek bir yandan da resim işleme sorumluluğuyla ilgilenmektedir. Tabi burada AWS Lambda işlevleri, S3 event’leri tarafından trigger edilmekte ve böylece zahmetsizce resimler üzerinde gerekli işlemler gerçekleştirilmektedir.

Umarım çözmeye çalışacağımız sorunu tam olarak anlatabilmişimdir… Bu sorun, özellikle yüksek trafikli uygulamalar için geçerlidir. Keza, S3 event’leri eşliğinde AWS Lambda entegrasyonunu izah edebilmek için de oldukça ideal bir senaryo gibi gözükmektedir.

Ne inşa edeceğiz?

Senaryomuz gereği inşa edeceğimiz uygulamanın yapısından da bahsetmekte fayda görmekteyim. Bir resim/image dosyasını herhangi bir Amazon S3 bucket’ına yükleyecek uygulama geliştireceğiz. Bu amaç doğrultusunda kullanıcıdan aldığı resim dosyasını S3 lokasyonuna yükleyecek olan minimal api teknolojisiyle geliştirilmiş bir ortam sağlayacak ve devamında resim yüklendiğinde de S3 event notification sistemini işleyecek ve yeni yüklenen resmi getirip boyutlandırma çalışmasını gerçekleştirecek olan bir AWS Lambda uygulaması geliştirilecektir. Ee haliyle notification kısmının sağlıklı çalışabilmesi için öncelikle AWS Lambda ve S3 bucket kısımlarını önceden yapılandırmış olmamız gerekecektir. Tüm bunların dışında resim işleme sürecinde popüler olan ImageSharp paketinden istifade edeceğiz.

Evet, hazırsak eğer hadi başlayalım…

Amazon S3 Bucket Oluşturma

İlk olarak çalışmalarımıza aşağıdaki görseldeki gibi resimleri depolayacağımız example-image-storage adında bir S3 bucket oluşturarak başlayabiliriz..NET'te S3 Event'leriyle AWS Lambda'yı Tetikleyelim

Resim Yükleme API’sini Oluşturma

Ardından resimleri bu S3 bucket’a yükleyecek olan minimal api projesini oluşturabiliriz. Bunun için ben AWS.Image.Example.API adında bir Asp.NET Core projesi oluşturuyorum. Ve akabinde bu projede S3 işlemleri için kullanacağımız aşağıdaki kütüphanelerin yüklenmesini sağlıyorum;
Install-Package AWSSDK.S3
Install-Package AWSSDK.Extensions.NETCore.Setup
Ve devamında ise aşağıdaki gibi resim yükleme işleminin endpoint’ini oluşturuyorum;

using Amazon.S3;
using Amazon.S3.Model;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddDefaultAWSOptions(builder.Configuration.GetAWSOptions());
builder.Services.AddAWSService<IAmazonS3>();

var app = builder.Build();

app.MapPost("/image-upload", async (IFormFile file, IAmazonS3 amazonS3) =>
{
    if (!file.ContentType.StartsWith("image/"))
        return Results.BadRequest("Dosya geçersizdir. Yalnız resim dosyaları desteklenmektedir.");

    PutObjectRequest request = new()
    {
        BucketName = "example-image-storage",
        Key = $"images/{Path.GetFileName(file.FileName)}",
        InputStream = file.OpenReadStream()
    };

    request.Metadata.Add("Content-Type", file.ContentType);
    await amazonS3.PutObjectAsync(request);
    return Results.Ok($"Resim başarıyla yüklenmiştir. | {Path.GetFileName(file.FileName)}");
}).DisableAntiforgery();

app.Run();

Yaptığımız bu çalışmayı test edersek eğer;.NET'te S3 Event'leriyle AWS Lambda'yı TetikleyelimGörüldüğü üzere endpoint’imiz başarıyla çalışmaktadır. S3 bucket’ı da kontrol edelim;.NET'te S3 Event'leriyle AWS Lambda'yı Tetikleyelim

Resim Boyutlandırma Lambda’sının Oluşturulması

Resimleri istenen S3 bucket’ına yükleyecek bir API oluşturduğumuza göre artık gereksinimimiz açısından resim boyutlandırma servisini oluşturabiliriz. Bunun için aynı solution altında, ‘Sizing.Example’ adında yeni bir AWS Lambda Project oluşturalım..NET'te S3 Event'leriyle AWS Lambda'yı TetikleyelimOluşturulan bu lambda’ya ImageSharp kütüphanesini yükleyelim ve içeriğini aşağıdaki gibi şekillendirelim;

public class Function
{
    IAmazonS3 S3Client { get; set; }

    public Function()
    {
        S3Client = new AmazonS3Client();
    }

    public Function(IAmazonS3 s3Client)
    {
        this.S3Client = s3Client;
    }

    public async Task FunctionHandler(S3Event evnt, ILambdaContext context)
    {
        var eventRecords = evnt.Records ?? new List<S3Event.S3EventNotificationRecord>();
        foreach (var record in eventRecords)
        {
            var s3Event = record.S3;
            if (s3Event == null)
            {
                continue;
            }

            try
            {
                var bucketName = s3Event.Bucket.Name;
                var key = s3Event.Object.Key;
                var response = await S3Client.GetObjectAsync(bucketName, key);
                context.Logger.LogLine($"{key} orjinal resim boyutu : {response.ContentLength}");

                using (var image = Image.Load(response.ResponseStream))
                {
                    int maxWidth = 500, maxHeight = 500;
                    image.Mutate(x => x.Resize(new ResizeOptions
                    {
                        Mode = ResizeMode.Max,
                        Size = new Size(maxWidth, maxHeight)
                    }));

                    using (MemoryStream stream = new MemoryStream())
                    {
                        image.Save(stream, new SixLabors.ImageSharp.Formats.Jpeg.JpegEncoder());
                        context.Logger.LogLine($"{key} boyutlandırılmış resim boyutu : {stream.Length}");
                        var resizedIamgesPath = $"resized-images/{key.Replace("images/", "")}";
                        var uploadRequest = new PutObjectRequest
                        {
                            BucketName = bucketName,
                            Key = resizedIamgesPath,
                            InputStream = stream
                        };
                        await this.S3Client.PutObjectAsync(uploadRequest);
                        context.Logger.LogLine($"Resim yeniden boyutlandırılmıştır.");
                    }
                }
                //Orjinal resmi silelim
                await this.S3Client.DeleteObjectAsync(bucketName, s3Event.Object.Key);
                context.Logger.LogLine($"Orjinal resim silinmiştir. {s3Event.Object.Key}");
            }
            catch (Exception e)
            {
                context.Logger.LogError(e.Message);
                context.Logger.LogError(e.StackTrace);
                throw;
            }
        }
    }
}

Yukarıdaki kod bloğunu incelersek eğer AWS S3 üzerindeki belirttiğimiz bucket’a yüklenen resimleri yeniden boyutlandırma işlemi yürütülmektedir. Burada bazı kodların işlevselliğinden bahsetmemizin faydalı olacağı kanaatindeyim. Misal olarak; evnt.Records, S3Event nesnesindeki tüm event kayıtlarını getirmektedir. Yani tetikleyici olayların listesidir diyebiliriz. record.S3 ise her bir S3EventNotificationRecord içindeki o olaya ait S3 bilgilerini temsil etmektedir. Örneğin, hangi bucket’a hangi dosyanın yüklendiği gibi bilgiler bu nesne üzerinden alınır. Son olarak s3Event.Object.Key komutunu izah edersek eğer, bu da dosyanın S3 içindeki yolunu temsil etmektedir. İşte bu bilgiler eşliğinde bizler S3 bucket’a yüklenen resim dosyası üzerinde gerekli işlemleri gerçekleştiriyor ve orjinal dosyayı silerek, boyutlandırılmış haliyle süreci tamamlıyoruz.

Evet, artık lambda function’ımızı oluşturduğumuza göre AWS’ye publish edebiliriz..NET'te S3 Event'leriyle AWS Lambda'yı TetikleyelimGörüldüğü üzere SizingExample isminde bir lambda function oluşturuyoruz..NET'te S3 Event'leriyle AWS Lambda'yı TetikleyelimBu aşamada rol olarak AWSLambdaExecute‘u seçmenizi tavsiye ederim. Böylece Cloudwatch’a log atabilmeniz için temel izinleri elde etmiş olacaksınız.

Ve nihai olarak bu şekilde lambda’yı publish edelim….NET'te S3 Event'leriyle AWS Lambda'yı Tetikleyelim

S3 Erişimi İçin Lambda İzinlerinin Güncellenmesi

Geliştirdiğimiz lambda tarafından S3 servisine erişip gerekli resim işlemlerinin gerçekleştirilebilmesi için doğal olarak bir takım izinlere ihtiyacımız olacaktır. Bu izinlerin düzenlenebilmesi için lambda servisi üzerinden oluşturduğumuz SizingExample isimli lambda’yı açalım ve aşağıdaki görselde olduğu gibi lambda rolüne tıklayarak erişim politikalarını düzenleyelim..NET'te S3 Event'leriyle AWS Lambda'yı TetikleyelimArdından Permissions policies kısmından ‘Add policies’ -> ‘Attach policies’ diyerek lambda’ya yeni erişim izni tanımlayalım..NET'te S3 Event'leriyle AWS Lambda'yı TetikleyelimBurada AmazonS3FullAccess seçerek yola devam edebiliriz..NET'te S3 Event'leriyle AWS Lambda'yı TetikleyelimEvet, artık lambda tarafından S3 erişimi verildiğine göre AWS Lambda’yı S3 Event’leriyle tetikleyebiliriz.

AWS Lambda’yı S3 Event’leriyle Tetikleme

Artık her şey yerli yerinde olduğuna göre, yapbozun son parçasını yerleştirebilir ve S3 bucket’ı altındaki ‘images’ klasörüne yeni bir resim yüklendiği taktirde lambda’mızı tetikleyebiliriz.

Bunun için de yine lambda üzerinden aşağıdaki görselde olduğu gibi ‘Configuration’ -> ‘Triggers’ sekmesine gelerek yeni bir trigger eklememiz gerekmektedir..NET'te S3 Event'leriyle AWS Lambda'yı TetikleyelimTrigger’la ilgili ayarları aşağıdaki görseldeki gibi yapılandıralım;.NET'te S3 Event'leriyle AWS Lambda'yı TetikleyelimVe trigger’ı oluşturalım..NET'te S3 Event'leriyle AWS Lambda'yı Tetikleyelimİşte bu kadar… Bu yaptığımız işlem neticesinde S3 bucket’ta ki ‘/images’ yoluna bir resim yüklendiği taktirde bu trigger sayesinde, S3Event meta verisi eşliğinde lambda tetiklenecektir. Vee gerekli resim boyutlandırma işlemini lambda halledecektir.

Test Edelim…

Şimdi S3 bucket’a oluşturduğumuz API aracılığıyla resim dosyasını gönderelim ve süreci CloudWatch loglarından takip edelim;.NET'te S3 Event'leriyle AWS Lambda'yı TetikleyelimGörüldüğü üzere resim orjinal haliyle yükleniyor, yeniden boyutlandırılıyor ve orjinali silinerek yeni hali ‘resized-images/’ path’ine koyuluyor..NET'te S3 Event'leriyle AWS Lambda'yı Tetikleyelim

Hata Durumlarında İse…

Eğer ki yapılan bu işlem sürecinde bir başarısızlık durumu söz konusu olursa AWS Lambda maksimum 6 saat içerisinde 2 kez deneme gerçekleştirecektir. Eğer ki bu denemelerden bir netice alınamazsa o dosyaya karşı oluşturulan event kaybolacaktır. Bu duruma dair tüm yapılandırmaları aşağıdaki görselde olduğu gibi lambda’nın ‘Configuration’ -> ‘Asynchronous invocation’ kısmından yapabilirsiniz..NET'te S3 Event'leriyle AWS Lambda'yı TetikleyelimBu durumu engelleyebilmek için Dead Letter Queue (DLQ) oluşturarak önlem alabilir ve yeniden denemeden sonra başarısız olan tüm event’leri belirtilen DLQ’ya taşıyarak, sonradan bu kuyruk üzerinden resimleri işleyebilirsiniz.

Nihai olarak;
Böylece S3 bucket’a yüklenen bir dosya üzerinden AWS Lambda’nın nasıl tetiklenebileceğini(trigger), dosya işleme mantığını ana uygulamadan ayırarak nasıl uygulanabileceğini ve genel sistem performansını iyileştirmenin temiz bir yolunu hep beraber tecrübe etmiş bulunuyoruz.

İlgilenenlerin faydalanması dileğiyle…
Sonraki yazılarımda görüşmek üzere…
İyi çalışmalar…

Not : Örnek çalışmaya aşağıdaki github adresinden erişebilirsiniz.
https://github.com/gncyyldz/AWS.Image.Example.API

Bunlar da hoşunuza gidebilir...

Bir yanıt yazın

E-posta adresiniz yayınlanmayacak. Gerekli alanlar * ile işaretlenmişlerdir