Windows Azure Üzerinde OWIN

   OWIN makale serisinde sizlere OWIN hakkındaki detayları vermeye çalışırken, Katana projesi yardımıyla da elimden geldiğince farklı örnekler paylaşmayı hedefledim. Daha önce Katana üzerinde, IIS üzerinde, Konsol ve Windows Forms uygulamalarımızda Katana yardımıyla sunduğumuz OWIN katmanları ve web uygulamalarımızı bu makalemde bulutta sunmayı planlıyorum. Hedefimiz Windows Azure üzerinde OWIN katmanlarımızı ve uygulamalarımızı sunmak…

   Windows Azure üzerinde Katana yardımıyla OWIN desteği sunmak temelde bir Windows hizmeti üzerinde sunmaktan farklı değil aslında. Bu sebeple Azure Worker Role üzerinden gitmek doğru olacaktır.

   Bu makaleyi okuyorsanız Windows Azure deneyiminizin olduğunu varsayıyorum; ama yine de hatırlatmakta fayda var, başlarken Windows Azure SDK’sının bilgisayarınızda kurulu olduğuna emin olun…

Visual Studio 2012 için Windows Azure SDK

   İşe öncelikle projemizi oluşturarak başlayalım;

Windows Azure Projemizi oluşturalım

   Projemizi oluştururken bizi karşılayan New Windows Azure Cloud Service diyaloğunda listemize sadece Worker Role’ü ekliyoruz;

"New Windows Azure Cloud Service" diyaloğu

   Proje açıldıktan sonra sıra geliyor NuGet paketlerini kurmaya;

Install-Package Microsoft.Owin.Hosting -Pre  
Install-Package Microsoft.Owin.Host.HttpListener -Pre
Install-Package Microsoft.AspNet.WebApi.Owin -Pre
Install-Package Microsoft.Owin.StaticFiles -Version 0.20-alpha-20220-88 -Pre

 

   Listeden de anlayacağınız üzere Azure Worker Role’ümüzde statik dosyalar kadar ASP.Net Web API’yi de kullanmayı planlıyorum. Sunucu olarak da her zaman ki gibi HttpListener kullanacağım.

   NuGet paketlerinin kurulması sonrası sırada servisimizin çıkış yapacağı uç nokta tanımlarının yapılmasında. Endpoint tanımlamaları sayesinde içerideki 80 (HTTP) portunu dış dünyaya yine 80 (HTTP) portuyla açacağız. Bu ayar için Enterprisecoding.AzureOWIN projesi Roles klasörü altında yer alan OWINRole’e gelerek bu rolün özelliklerini açalım.

Endpoint ayarları

   Açılan pencereden Endpoints segmesine gelerek Add Endpoint butonu yardımıyla aşağıdaki özelliklerle yeni bir endpoint ekleyelim;

Name OWINHizmeti
Type Input
Protocol http
Public Port 80
Private Port 80

   Bu basit ayarlardan sonra sıra geldi standart OWIN yapılandırma adımlarını takip ederek katmanlarımızı belirlemeye. Her zamanki gibi öncelikle OWINRole projemizde Startup sınıfımızı oluşturalım;

using System.Web.Http;
using Owin;

namespace OWINRole {
    internal class Startup {
        public void Configuration(IAppBuilder app) {
            var config = new HttpConfiguration();

            config.Routes.MapHttpRoute(
                "Default",
                "api/{controller}/{id}",
                new {id = RouteParameter.Optional});

            app.UseWebApi(config);
        }
    }
}

   Test için seçtiğim controller ise bir önceki makalemde kullandığım YapilacaklarController olacak;

using System.Collections.Generic;
using System.Web.Http;

namespace OWINController {
    public class YapilacaklarController : ApiController {
        public IEnumerable<Yapilacak> Get() {
            return new[] {
                new Yapilacak { Id = 1, Content = "OWIN makaleleri  yaz", Tamamlandi = true }, 
                new Yapilacak { Id = 2, Content = "Roslyn scripting örneği kodla", Tamamlandi = true },
                new Yapilacak { Id = 3, Content = "Geri bildirimleri yanıtla", Tamamlandi = false },
                new Yapilacak { Id = 4, Content = "Tatilin tadını çıkar", Tamamlandi = false }
            };
        }
    }

    public class Yapilacak {
        public int Id;
        public string Content;
        public bool Tamamlandi;
    }
}

   Şimdi sıra geldi sunucumuzu başlatmaya. Bunun için WorkerRole sınıfımız içerisine WebApp örneğinin referansını tutmak için IDisposable türünden webApp değişkenini ekleyelim. Bunun nedeni, diğer projelerimizden farklı olarak bir Azure Worker Role’ün aynı Windows Hizmetlerinde olduğu gibi uygulamayı başlatan ve bitiren noktaların iki ayrı fonksiyon olmasıdır. bu sebeple tüm süreci tek bir using bloğu içine alamayız. OnStart ile hizmeti başlatan kodu yazarken, OnStop ile bu hizmeti sonlandırıp kullandığımız kaynakları da işletim sistemine geri iade etmeliyiz. Öncelikle hizmetimizi başlatalım;

public override bool OnStart() {
    // Set the maximum number of concurrent connections 
    ServicePointManager.DefaultConnectionLimit = 12;

    // For information on handling configuration changes
    // see the MSDN topic at http://go.microsoft.com/fwlink/?LinkId=166357.


    webApp = WebApp.Start<Startup>("[URL]");


    return base.OnStart();
}

   Dikkat ederseniz hizmetimizin açılacağı url’yi belirtmedim. Bunun nedeni derleme sırasında Azure Worker Role’ümüze Windows Azure tarafından hangi endpoint’in atanacağını bilmiyor olmam. Bu bilgileri yapılandırmadan RoleEnvironment yardımıyla aşağıdaki gibi çekebiliriz;

var ucNokta = RoleEnvironment.CurrentRoleInstance.InstanceEndpoints["OWINHizmeti"];

   Son haliyle OnStart fonksiyonumuz aşağıdaki şekilde olacaktır;

public override bool OnStart() {
    // Set the maximum number of concurrent connections 
    ServicePointManager.DefaultConnectionLimit = 12;

    var ucNokta = RoleEnvironment.CurrentRoleInstance.InstanceEndpoints["OWINHizmeti"];

    webApp = WebApp.Start<Startup>(ucNokta.Protocol + "://" +ucNokta.IPEndpoint);

    return base.OnStart();
}

 

   Hizmetimizin durdurulduğu OnStop fonksiyonu içerisinde de servisimize ait kaynakları işletim sistemine iade etmeliyiz;

webApp.Dispose();
webApp = null;

     Son haliyle WorkerRole sınıfımız aşağıdaki şekilde olacaktır;

using System;
using System.Diagnostics;
using System.Net;
using System.Threading;
using Microsoft.Owin.Hosting;
using Microsoft.WindowsAzure.ServiceRuntime;

namespace OWINRole {
    public class WorkerRole : RoleEntryPoint {
        private IDisposable webApp;

        public override void Run() {
            // This is a sample worker implementation. Replace with your logic.
            Trace.WriteLine("OWINRole entry point called", "Information");

            while (true) {
                Thread.Sleep(10000);
                Trace.WriteLine("Working", "Information");
            }
        }

        public override bool OnStart() {
            // Set the maximum number of concurrent connections 
            ServicePointManager.DefaultConnectionLimit = 12;

            var ucNokta = RoleEnvironment.CurrentRoleInstance.InstanceEndpoints["OWINHizmeti"];

            webApp = WebApp.Start<Startup>(ucNokta.Protocol + "://" + ucNokta.IPEndpoint);

            return base.OnStart();
        }

        public override void OnStop() {
            if (webApp != null) {
                webApp.Dispose();
                webApp = null;
            }

            base.OnStop();
        }
    }
}

   Şimdi sıra geldi statik içeriğimize.. Bunun için yine bir önceki makalemde kullandığım index.html’i kullanacağım;

<!DOCTYPE html>

<html lang="en" xmlns="http://www.w3.org/1999/xhtml">
    <head>
        <meta charset="utf-8" />
        <title>Enterprisecoding - OWIN Arayüz Örneği</title>
        <script src="http://code.jquery.com/jquery-2.0.0.min.js"></script>
    </head>
    <style>
        /* http://meyerweb.com/eric/tools/css/reset/ 
   v2.0 | 20110126
   License: none (public domain)
 */

html, body, div, span, applet, object, iframe, h1, h2, h3, h4, h5, h6, p, blockquote, pre, a, abbr, acronym, address, big, cite, code, del, dfn, em, img, ins, kbd, q, s, samp, small, strike, strong, sub, sup, tt, var, b, u, i, center, dl, dt, dd, ol, ul, li, fieldset, form, label, legend, table, caption, tbody, tfoot, thead, tr, th, td, article, aside, canvas, details, embed, figure, figcaption, footer, header, hgroup, menu, nav, output, ruby, section, summary, time, mark, audio, video {
    margin: 0;
    padding: 0;
    border: 0;
    font-size: 100%;
    font: inherit;
    vertical-align: baseline;
}

article, aside, details, figcaption, figure, footer, header, hgroup, menu, nav, section { display: block }

body { line-height: 1 }

ol, ul { list-style: none }

blockquote, q { quotes: none }

blockquote:before, blockquote:after, q:before, q:after {
    content: '';
    content: none;
}

table {
    border-collapse: collapse;
    border-spacing: 0;
}

/* * Copyright (c) 2012 Thibaut Courouble
 * http://www.webinterfacelab.com
 *
 * Licensed under the MIT License:
 * http://www.opensource.org/licenses/mit-license.php
   ================================================== */

/* ========================================================
   Page
   =====================================================
   ================================================== */

body {
    background: #f8f6f6;
    color: #404040;
    font-family: 'Lucida Grande', Verdana, sans-serif;
    font-size: 13px;
    font-weight: normal;
    line-height: 20px;
}

a {
    color: #1e7ad3;
    text-decoration: none;
}

a:hover { text-decoration: underline }

.container {
    margin: 50px auto;
    width: 380px;
}

/* ========================================================
   Icons
   =====================================================
   ================================================== */

[class^="icon-"], [class*=" icon-"] {
    display: inline-block;
    width: 12px;
    height: 12px;
    vertical-align: -2px;
    margin-right: 2px;
    background-image: url("http://demo.webinterfacelab.com/14-to-do-list/img/sprite.png");
    background-repeat: no-repeat;
    text-indent: 100%;
    white-space: nowrap;
    overflow: hidden;
}

.icon-check { background-position: 0 0 }

.icon-add { background-position: -12px 0 }

.icon-delete { background-position: -24px 0 }

.icon-settings { background-position: -36px 0 }

.icon-previous { background-position: -48px 0 }

.icon-next { background-position: -60px 0 }

/* ========================================================
   Todo List
   =====================================================
   ================================================== */

.todo {
    position: relative;
    width: 260px;
    margin: 0 auto;
    padding: 12px 0;
    background: #fff;
    border: 1px solid;
    border-color: #dfdcdc #d9d6d6 #ccc;
    border-radius: 2px;
    -webkit-box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1);
    -moz-box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1);
    box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1);
}

.todo:before, .todo:after {
    content: '';
    position: absolute;
    z-index: -1;
    height: 4px;
    background: #fff;
    border: 1px solid #ccc;
    border-radius: 2px;
}

.todo:after {
    left: 0;
    right: 0;
    bottom: -3px;
    -webkit-box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1);
    -moz-box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1);
    box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1);
}

.todo:before {
    left: 2px;
    right: 2px;
    bottom: -5px;
    border-color: #c4c4c4;
    -webkit-box-shadow: 0 1px 2px rgba(0, 0, 0, 0.15);
    -moz-box-shadow: 0 1px 2px rgba(0, 0, 0, 0.15);
    box-shadow: 0 1px 2px rgba(0, 0, 0, 0.15);
}

.lt-ie9 .todo:before, .lt-ie9 .todo:after {
    height: 1px;
    border-top: 0;
}

.todo-list { border-top: 1px solid #e6ebed }

.todo-list:before {
    content: '';
    width: 3px;
    z-index: 2;
    border: 1px solid #f2e3df;
    border-width: 0 1px;
    position: absolute;
    top: 0px;
    bottom: 0px;
    left: 35px;
}

.todo-list li {
    position: relative;
    padding: 7px 15px 7px 50px;
    line-height: 21px;
    font-size: 12px;
    color: #8b8f97;
    border-bottom: 1px solid #e6ebed;
}

input[type=checkbox] {
    display: none;
}

input[type=checkbox]:checked + .toggle:after{
    opacity: 1;
}

.todo-list .toggle {
    display: block;
    height: 35px;
    width: 35px;
    position: absolute;
    top: 0px;
    bottom: 0px;
    left: 0px;
    text-indent: 100%;
    overflow: hidden;
    cursor: pointer;
}

.toggle:after {
    content: '';
    position: absolute;
    position: absolute;
    width: 7px;
    height: 3px;
    background: transparent;
    top: 14px;
    left: 13px;
    border: 2px solid #aaa;
    border-top: none;
    border-right: none;

    -webkit-transform: rotate(-45deg);
    -moz-transform: rotate(-45deg);
    -o-transform: rotate(-45deg);
    -ms-transform: rotate(-45deg);
    transform: rotate(-45deg);
    opacity: 0;
}

.todo-list .toggle:before {
    content: '';
    width: 15px;
    height: 15px;
    background: #faf9f9;
    border: 1px solid #6bb3ca;
    border-radius: 2px;
    position: absolute;
    top: 9px;
    left: 9px;
    -webkit-box-shadow: 0 1px 1px #dfecf4;
    -moz-box-shadow: 0 1px 1px #dfecf4;
    box-shadow: 0 1px 1px #dfecf4;
}

.todo-list .toggle:hover:before {
    -webkit-box-shadow: 0 0 3px #6bb3ca;
    -moz-box-shadow: 0 0 3px #6bb3ca;
    box-shadow: 0 0 3px #6bb3ca;
}

.todo-list .done .toggle:before, .todo-list .toggle:active:before {
    border-color: #c0c0c0 #ccc #d8d8d8;
    -webkit-box-shadow: inset 0 1px rgba(0, 0, 0, 0.05), inset 0 5px 5px rgba(0, 0, 0, 0.05);
    -moz-box-shadow: inset 0 1px rgba(0, 0, 0, 0.05), inset 0 5px 5px rgba(0, 0, 0, 0.05);
    box-shadow: inset 0 1px rgba(0, 0, 0, 0.05), inset 0 5px 5px rgba(0, 0, 0, 0.05);
}
.todo-controls {
    margin: 0 15px 12px 50px;
    height: 12px;
}

.todo-controls li { float: left }

.todo-controls li + li { margin-left: 10px }

.todo-controls .right { float: right }

.todo-controls a {
    display: block;
    margin: 0;
    opacity: .6;
}

.todo-controls a:hover { opacity: 1 }

.todo-pagination {
    margin: 12px 12px 0 50px;
    height: 22px;
}

.todo-pagination li { float: left }

.todo-pagination .next { float: right }

.todo-pagination .next i { margin: 0 0 0 2px }

.todo-pagination a, .todo-pagination span {
    display: block;
    line-height: 22px;
    font-size: 11px;
    color: #676f7f;
}

.todo-pagination a {
    padding: 0 8px;
    text-shadow: 0 1px #fff;
    background: #f1f0f0;
    border-radius: 3px;
}

.todo-pagination a:hover {
    background: #e9e9e9;
    text-decoration: none;
}

.todo-pagination span {
    padding: 0 4px;
    opacity: .3;
}

/* ========================================================
   About
   =====================================================
   ================================================== */

.about {
    margin: 80px auto 50px;
    padding: 15px 20px;
    width: 300px;
    text-align: center;
    color: #777;
    text-shadow: 0 1px rgba(255, 255, 255, 0.7);
    background: rgba(0, 0, 0, 0.05);
    border-radius: 3px;
}

.about a {
    padding: 1px 3px;
    margin: 0 -1px;
    color: #1c74c8;
    text-decoration: none;
    border-radius: 2px;
}

.about a:hover {
    color: #fff;
    text-shadow: 0 1px #0063A6;
    background: #0090D2;
}

.links { zoom: 1 }

.links:before, .links:after {
    content: "";
    display: table;
}

.links:after { clear: both }

.links a {
    padding: 6px 0;
    float: left;
    width: 50%;
    font-size: 14px;
}

.author {
    margin-top: 15px;
    font-size: 11px;
}
    </style>
<body>
    <script type="text/javascript">
        function YapilacaklariGetir() {
            $.ajax({
                url: '/api/yapilacaklar',
                type: 'GET',
                dataType: 'json',
                success: function (data) {
                    YapilacaklariYaz(data);
                },
                error: function (x, y, z) {
                    alert(x + '\n' + y + '\n' + z);
                }
            });
        }

        function YapilacaklariYaz(yapilacaklar) {
            var sonuc = "";

            $.each(yapilacaklar, function (index, yapilacak) {
                sonuc += yapilacak.Tamamlandi ?
                    "<li class='done'><input type='checkbox' id='todo" + yapilacak.Id + "' checked disabled />" :
                    "<li><input type='checkbox' id='todo" + yapilacak.Id + "'/>";
                sonuc += "<label class='toggle for='todo" + yapilacak.Id + "'></label>" + yapilacak.Content + "</li>";
            });

            $(".todo-list").html(sonuc);

        }
    </script>
    <div class="container">
    <section class="todo">
        <ul class="todo-controls">
            <li><a href="javascript:void(0);" class="icon-add">Add</a></li>
            <li><a href="javascript:void(0);" class="icon-delete">Delete</a></li>
            <li class="right"><a href="javascript:YapilacaklariGetir();" class="icon-settings">Settings</a></li>
        </ul>
    
        <ul class="todo-list"></ul>
        
        <ul class="todo-pagination">
            <li class="previous"><span><i class="icon-previous"></i> Geri</span></li>
            <li class="next"><a href="javascript:void(0);">İleri <i class="icon-next"></i></a></li>
        </ul>
    </section>
</div>
</body>
</html>

   Dikkat ettiyseniz bu defa ajax isteğini yaptığım javascript kodunda Web API adresini tam vermek yerine ‘/api/yapilacaklar‘ şeklinde verdim. Bunun nedeni Worker Role’ün hizmet vereceği tam adresi bilmemem.. Son olarak da; önceden olduğu gibi bu dosyayı da her derlemede çıktı klasörüne kopyalanacak şekilde ayarlayalım.

   Bu kadar… Sonrasında projemizi F5 ile başlatıp yerel emulator’de başlatarak web browser üzerinden sonucu görebilirsiniz…

Projeyi debug için başlatıp web browser'dan sayfamızı açtığımızda karşımıza uygulamamız gelecektir

 

Uygulamamız arkaplanda çalışan ASP.Net Web API controller'ımızdan ajax ile çektiği verileri gösterirken

   Gördüğünüz gibi Katan yardımıyla OWIN katmanlarını Windows Azure üzerinde sunmak oldukça kolay. Tüm süreç Windows hizmetinde sunmakla aynı…

 

   Makalemin devamında hazırladığımız Azure Worker Role’ü Windows Azure’a deploy etme adımlarını paylaşacağım. Bu konuda deneyim sahibi olanlar bu kısmı geçebilirler…

 

   Azure Worker Role’ümüzü hazırlayıp test ettikten sonra sıra geldi bunu deploy etmeye. Eğer çoktan bir Bulut Hizmeti (Cloud Service) oluşturmadıysanız ilk adım yönetim portaline geçip bir tane oluşturmak olmalı. New –> Cloud Service –> Quick Create adımları ardından yeni bir bulut hizmeti oluşturma ekranı bizi karşılayacaktır;

Yeni bir Bulut Hizmeti (Cloud Service) oluşturma ekranı

   Hızlıca bir hizmet adresi ve hizmetin sunulacağı bölgeyi seçtikten sonra Create Cloud Service seçeneği yardımıyla bulut hizmetimizi oluşturabiliriz.

Bulut Hizmeti (Cloud Service) oluştu

      Şimdi tekrar Visual Studio’ya, projemize geri dönerek Solution Explorer’dan Enterprisecoding.AzureOWIN’i seçerek sağ tıklama menüsünden Publish seçeneğini seçiyoruz. Bu seçenek karşımıza Windows Azure Publish diyaloğunu getirecektir;

"Windows Azure Publish" diyaloğu

   Daha önceden bir yayınlama (publish) işlemi yaptıysanız subscription bölümü dolu gelecektir. İlk defa yapıyorsanız merak etmeyin Sign in to download credentials linkine tıklayıp yapılandırma dosyasını indirebilir ve Import butonu yardımıyla da bu yapılandırmayı alabilirsiniz. Sonraki adım Settings diyaloğu;

"Windows Azure Publish" diyaloğu Ayarlar segmesi

   Son olarak özet ekranı bizi bekliyor…

"Windows Azure Publish" diyaloğu Özet segmesi

 

 

   Özet ekranında yer alan Publish butonu yayınlama sürecini başlatacaktır. Sürecin devamını Windows Azure Activity Log penceresinden izleyebilirsiniz;

Visual Studio içerisinde "Windows Azure Activity Log" penceresi üzerinden publish sürecini takip edebilirsiniz

    Başarılı bir yayınlama sonrasında artık bir web browser üzerinden yayınladığımız adresi açarak sonucu görebiliriz;

Az önce yayınladığımız uygulamamızı http://owintest.cloudapp.net adresinde görebiliriz

 

   Aşağıdaki ekran görüntüsünde yapılan ajax çağrısını da görebilmeniz mümkün;Bulut hizmetinde bulunan uygulamamızın ASP.Net Web API çağrıları yaptığını da görebilirsiniz

Fatih Boy

Ankara'da yaşayan Fatih, bir kamu kurumunda danışman olarak çalışmaktadır. ALM süreçleri, kurumsal veri yolu sistemleri, kurumsal altyapı ve yazılım geliştirme konularında destek vermektedir. Boş zamanlarında açık kaynak kodlu projeler geliştirmeyi ve bilgisini yazdığı makalelerle paylaşmayı seven Fatih, aynı zamanda Visual C# ve Visual Studio teknolojileri konusundan Microsoft tarafından altı yıl üst üste MVP (En Değerli Profesyonel) ödülüne layık görülmüştür. İş hayatı boyunca masaüstü uygulamaları, web teknolojileri, akıllı istemciler gibi konularda Asp.Net, Php, C#, Java programlama dilleri ve MySql, MsSql ve Oracle gibi veritabanı yönetim yazılımları ile çalışmıştır. İngilizce ve Türkçe olarak yayınlanan makalelerini gerek İngilizce bloğunda, gerekse de Türkçe bloğunda bulabileceğiniz gibi web sitesinden de açık kaynak kodlu geliştirdiği yazılımlarına ulaşabilirsiniz. vCard - Twitter - Facebook - Google+

3 yorum

  1. Emre   •  

    Azure’u daha net anlamak için soruyorum. Bu servisi Cloud serviste yazmanın, Azure VM’de yazmaya göre fiyat hariç ne tür avantajları var?

    • Fatih Boy   •     Yazar

      Merhaba Emre,
      Her ne kadar Worker Role ile VM karşılaştırması aslında başlı başına bir konu olsa da, dilim döndüğünce bakış açımı paylaşmaya çalışayım. Teknik olarak baktığımızda aslında Windows Azure’da tüm hesaplama kaynakları (böyle deyince de kulağa hoş gelmiyor, Compute Resources) sanal makineler üzerinde çalışmaktadır. Öte yandan baktığımızda ise ben, geliştirici olarak, Windows Azure’dan bir Worker Role talep ederken aslında şunu diyorum; Altyapı’da ne çalıştırdığınız, işletim siteminin ne olduğu ve hatta güncelleme ve güvenliğinin nasıl sağlandığı ile ilgilenmek istemiyorum. Yani aslında ben, Worker Role ile çalışmak istediğimi belirttiğimde, aslında Platform as a Service (PaaS) istiyorum.
      Öte yandan VM Rolü farklı bir ihtiyaç. Şahsen bir geliştirici olarak uygulamamı VM role üzerinde çalıştırmak istiyorsam, bu durumda tüm kontrolü istiyorum demektir; ki buna işletim sistemi de dahil. Hatta şunu rahatlıkla söyleyebilirim; ben işletim sistemine tam hakim olmak için VM rolünü istiyorum. Bu sayede istediğim gibi at koşturabildiğim bir makine olur. Yani ben, geliştirici olarak, bir VM rolü talep ederken aslında Infrastructure as a Service (IaaS) istiyorum. Bunu isterken işletim sisteminin güvenliği, güncelleği v.b. konuları tabi ki tamamen benim sorumluluğumda oluyor, aksi olarak ilk seçenekte bunların hiç birini düşünmeme gerek yok…

      Toparlarsak, sorunun yanıtı fiyat avantajı değil… Hatta kesinlikle değil… Burada seçimini netleştirecek olan senin IaaS mi PaaS mi istediğin.. Geliştirici/Firma olarak nereye kadar sorumluluk/kontrol istediğin…

      • Emre   •  

        Teşekkür ederim. Daha net anladim sayenizde.

Bir Cevap Yazın

E-posta hesabınız yayımlanmayacak. Gerekli alanlar * ile işaretlenmişlerdir