Blog Kokosa

.NET i okolice, wydajność, architektura i wszystko inne

NAVIGATION - SEARCH

Niedzielnik Kokosa czyli Azure Logic Apps i Azure Functions

Właściwie odkąd założyłem bloga, chodził za mną temat zautomatyzowanych, cotygodniowych postów dotyczących najciekawszych treści, na które natknąłem się danego tygodnia w Internecie. Nadałem temu nazwę “Niedzielnik Kokosa” bo postanowiłem publikować je w niedzielę rano – doskonały moment na spokojne przejrzenie ciekawych treści. Ponieważ na co dzień mam zwyczaj odkładania ciekawych linków do Instapaper – źródło danych mam gotowe. Pozostawała tylko kwestia automatyzacji. Dość długo czaiłem się na serwisy pokroju IFTTT albo Zapier. Jednak z każdą kolejną próbą odbijałem się od nich, ponieważ potrzebna mi automatyzacja (choć prosta) nie wpisywała mi się w to co te serwisy oferują.

To czego potrzebuję to następującą sekwencję zdarzeń (wykonywaną cyklicznie, np. rano co niedzielę):

  1. Pobierz z mojego konta Instapaper linki zapamiętane przeze mnie w ciągu ostatnich 7 dni – maksymalnie 10
  2. Opublikuj na blogu post zgodnie z pewnym szablonem, wypisującym te linki
  3. Opublikuj na Twitterze i stronie Facebooka informację o nowym poście
  4. Idealnie – poinformuj mnie mailem, że się udało

Funkcjonalność taką udało mi się w bardzo przyjemny i szybki sposób zaimplementować ostatnio za pomocą kombinacji dwóch świetnych usług Azure:

  • Azure Logic Apps – pozwala na definiowanie sekwencji akcji (włączając to w pętle, warunki itp.) składających się z wielu dostępnych “z pudełka” komponentów. Są m.in. dostępne komponenty integrujące z pocztą, Facebookiem, Twitterem, Instapaper (z metodami typu “pobierz wszystkie polubione treści”) i wieloma, wieloma innymi. Przepływy (choć angielski workflow dużo lepiej tu pasuje) definiować można za pomocą JSONa lub graficznie za pomocą zaskakująco przyjemnego edytora.
  • Azure Functions – pozwala na zdefiniowanie funkcji (napisanych w C#, F#, Node.js), które w duchu “serverless” hostowane są w Azure i możemy ich reużywać za pomocą HTTP. Co dla mnie ważne, jednym z klocków w Azure Logic App może być właśnie stworzona przez nas Azure Function.

Tak doprowadziło mnie to do zdefiniowania w Logic App następującego przepływu:

niedzielnik

Jak dla mnie bomba, zwłaszcza że tworzenie go było szybkie i napotkałem zaskakująco mało problemów. Sam diagram jest dość oczywisty. Klikając na każdy z bloczków można podać dodatkowe parametry, tutaj większość zwinąłem dla przejrzystości.

Głównym triggerem jest akcja Recurrence, uruchamiająca przepływ co siedem dni począwszy od najbliższej niedzieli:

niedzielnik2

Następna akcja to integrator z Instapaper, który po prostu pobiera polubione treści z mojego konta Instapaper. Postanowiłem użyć mechanizmu polubienia by świadomie wybierać co spośród wszystkich zapamiętanych treści ma szansę się pojawić w Niedzielniku. Następnie pierwsza z Azure Functions – ProcessLikes. Filtruje ona otrzymanego z Instapaper JSONa. Cały jej kod wygląda następująco:

#r "System.Net.Http"
#r "Newtonsoft.Json"

open System.Net
open System.Net.Http
open Newtonsoft.Json

type Bookmark = {
    hash: string
    description: string
    bookmark_id: string
    private_source: string
    title: string
    url: string
    progress_timestamp: string
    time: DateTime
    progress: string
    starred: bool
    ``type``:string    
}

let Run(req: HttpRequestMessage, log: TraceWriter) =
    async {
        let days = match req.GetQueryNameValuePairs()
                         |> Seq.tryPick(fun p -> if p.Key = "days" then Some(p) else None) with
                   | Some p -> p.Value
                   | None -> "7"
                   |> float

        let! jsonContent = req.Content.ReadAsStringAsync() |> Async.AwaitTask

        try
            let bookmarks = JsonConvert.DeserializeObject<Bookmark[]>(jsonContent)
            let now = DateTime.Now.AddDays(-1.0*days)
            let results = bookmarks
                          |> Seq.filter (fun b -> b.time > now)
                          |> Seq.truncate 10
            return req.CreateResponse(HttpStatusCode.OK, results)
        with _ ->
            return req.CreateResponse(HttpStatusCode.BadRequest)
    } |> Async.StartAsTask

Kolejna funkcja – PublishLikes – używa paczki MetaWeblogSharp by poprzez API MetaWeblog (o dziwo wspierane nawet przez BlogEngine.NET) stworzyć nowy wpis:

let date = DateTime.Now
let title = sprintf "Niedzielnik Kokosa - %s" (date.ToString("dd-MM-yyyy"))
let data = { Title = title; Bookmarks = bookmarks }
let lines = [
    @"<div style=""text-align: center;""><img src=""/image.axd?picture=%2f2017%2f03%2fniedzielnik_logo.jpg""></div>";
    @"<p>Internet codziennie zalewa nas informacjami. Jeśli nie masz czasu tego wszystkiego ogarnąć, oto garść wybranych przeze mnie, najciekawszych treści, na które natknąłem się w tracie ostatniego tygodnia:<br /></p>";
    sprintf @"<ul>%s</ul>" (Seq.fold (fun s (x : Bookmark) -> (s + sprintf @"<li><a href=""%s"">%s</a> - %s</li>" x.url x.title x.description)) String.Empty data.Bookmarks)
]
let html = String.concat "" lines 

let conn = new MetaWeblogSharp.BlogConnectionInfo(
    "http://blog.kokosa.net",
    "http://blog.kokosa.net/metaweblog.axd",
    "BlogKokosa",
    user,
    password)

let client = new MetaWeblogSharp.Client(conn)

let newPost = new PostInfo(Title = title,
                           DateCreated = new Nullable<DateTime>(date),
                           Description = html)
let postId = client.NewPost(newPost, [| "Niedzielnik"; "Ciekawostki" |], true)
let postDesc = client.GetPost(postId)
let result = { PostId = postDesc.PostID; Url = postDesc.Link }

Z braku czasu użyłem dość paskudnego podejścia z użyciem sprintf. Niestety z niewiadomych mi jeszcze przyczyn nie udało się użyć np. DotLiquid – paczka nie chciała się zaimportować z nugeta.

Dalej jest jeszcze prościej. Jeśli odpowiedź z PublishLikes jest 200 OK, to za pomocą integracji z Bufferem kolejkuję publikację informacji o nowym Niedzielniku na Twitterze i na Facebooku (link do opublikowanego posta dostaję z funkcji PublishLikes):

niedzielnik3

Następnie dla każdego udanej publikacji wysyłam maila podsumowującego całą akcję. Między publikacją posta, a użyciem Buffera, dodałem krótkie opóźnienie rzędu 30 sekund bo nie ufam BlogEngine.NET i wolę dać mu chwilę na faktyczne opublikowanie posta.

I to tyle! Coś na co czaiłem się od miesięcy udało mi się wyklikać w kilka godzin zaczynając od zera ze znajomością obu usług Azure. Najwięcej czasu i tak poświęciłem na fragmenty w F# bo wciąż go dopiero opanowuję. I tak wiem, nadal brzydko ten kod wygląda Puszczam oczko

Tak czy inaczej, teraz pozostaje już tylko czekać na niedzielę 26 marca i o godzinie 10 przekonać się czy pierwszy Niedzielnik faktycznie się ukaże!

PS. To czym chciałbym się jeszcze pobawić to lepszym logowaniem i monitorowaniem tego procesu więc CDN.

blog comments powered by Disqus