{"action":"created","comment":{"url":"https://api.github.com/repos/Galeria-Kaufhof/galeria-kaufhof.github.io/pulls/comments/33313796","id":33313796,"diff_hunk":"@@ -0,0 +1,167 @@\n+---\n+layout: post\n+title: \"Keeping things together: a \\\"frontend middleware\\\" on top of a shared-nothing architecture\"\n+description: \"Managing the technical frontend layer on top of a vertically cut, shared-nothing architecture\n+is a serious challenge and keeping the idea of DRY alive and kicking among entirely separated teams\n+requires quite a lot of convention and discipline. Read on to learn how we approached this problem.\"\n+category: general\n+author: ricopfaus\n+tags: [javascript,coffeescript,tracking,analytics,affiliates,tagmanager,frontend]\n+---\n+{% include JB/setup %}\n+\n+## The challenge\n+Before I explain what exactly a *\"frontend middleware\"* is, let me first tell you why we invented it and why everybody should use one. \n+\n+About one year ago, when I joined the e-commerce team at GALERIA Kaufhof working on their new online platform, everything was still in its early stages. The idea of having totally separated application \"verticals\" was the crazy new thing and the entire system was cut into microservices to keep the business logic separated. From a backend and business perspective this perfectly made sense.\n+\n+But I realized very quickly that - from a frontend view - having five entirely separated functional teams work on things like integrating tracking solutions, affiliates or any other third-party content wasn't very efficient. Five independent frontend devs had to individually learn tracking APIs, implement tracking snippets, manage things like accounts within their own application's context. You should get the point. Sounded like the same job done multiple times by multiple people.\n+\n+## The \"data layer approach\"\n+Working with tag management systems before, I got used to what I'd call the \"data layer approach\" pretty well. To put it simple: the website renders some kind of interesting business information (e.g. product attributes or basket contents) into its page body. Often this is done using some Javascript API. The tagmanager then takes this data and hands it over to third-party tools like analytics, affiliates or alike. A very simple example, illustrating the concept:\n+\n+{% highlight javascript %}\n+// common example of a \"data layer\"\n+var DataLayer = window.DataLayer || [];\n+DataLayer.push({\n+  product:{\n+    id: 1234567890,\n+    name: \"Book\",\n+    price: 19.95\n+  }\n+});\n+{% endhighlight %}\n+ \n+This technique felt ideal to approach our separation issue. Each vertical application could independently render data into the markup and some frontend logic would take the data and care for the rest. While thinking about it, we wanted to take it even one step further and do fancy things like declarative tracking, channel recognition and tag management within our own context. Appeared seriously cool.\n+\n+## Meta tags to the rescue\n+From a technical perspective things were pretty obvious. We needed some API that the application could utilize for transporting information (and later events, too) into the client space. Also it had to be completely generic and shouldn't apply any of the structural decisions (i.e. \"verticals\") into the API. An additional scripting API to call from within custom controllers would be nice, but not first priority.\n+\n+Being modern frontend guys, we always avoid inlined script tags whenever possible. So we decided to not rely on Javascript for passing the data around. Instead we use dedicated metatags containing JSON data for handing over information from the markup to our *frontend middleware* (which btw we called *\"Data Abstraction Layer\"* or DAL). An example metatag could look like this:\n+\n+{% highlight XML %}\n+<!-- example for a page data object -->\n+<meta name=\"gk:dal:data\" content='{\n+  \"page\":{\n+    \"type\":\"homepage\",\n+    \"name\":\"Startseite\"\n+  }}' />\n+{% endhighlight %}\n+\n+This also gave us the great advantage that each vertical application could independently render any kind of data anywhere into a page's markup (even within SSI contexts) without worrying about availability or load order of a library or API. Markup loads reliably earlier than scripts, at least in most common browsers ;-) ... For asynchronously loaded content we developed another, Javascript-driven solution based on broadcasting events top-down from the DAL to its plugins. I'll talk about that later.\n+\n+## Plugins, Rules and Repos\n+Behind the scenes and concepts the DAL is pretty lightweight and straightforward. From a technical perspective it is just an advanced plugin loader, lazy loading its plugins using requirejs. On initialization it collects all metatags from the page, aggregates the contained data into one large object, loads its plugins depending on supplied rules and hands over data to each of them. The more specific magic is then going on inside the plugins' code.\n+\n+The plugins are normal AMD-modules returning a class with at least a constructor and a `handleEvent` method. When instantiated, the constructor receives a reference to the DAL module, the aggregated page and an optional configuration object. The `handleEvent` method does all the magic required for handling asynchronous events happening within a page's lifecycle. A simple stub plugin could look like the following code (this time in [Coffescript](http://coffeescript.org)):\n+\n+{% highlight coffeescript %}\n+define \"gk/lib/dal/demoAffiliate\", [\"thirdpartylib\"] (thirdpartyLib) ->\n+  \n+  # A simple demo plugin. Just provides a class container with example plugin logic\n+  # @implements IDALService\n+  class DemoAffiliate\n+    \n+    # init our third party lib\n+    constructor: (@dal, @data, @config)\n+      thirdpartyLib.init(\"someaccountid\")\n+    \n+    # handle async events\n+    handleEvent: (name, data, domain) ->\n+      if name is \"addtocart\":\n+        # notify my third-party backend about this event\n+        thirdpartyLib.notify(\"addtocart\", data.product.id)\n+          \n+{% endhighlight %}\n+\n+The plugin loading is based on a simple ruleset which, optionally, executes callbacks before loading a plugin. That allows for very advanced serve rule logic, which no tagmanager could offer out-of-the-box (e.g. *if the page's type is \"checkout-complete\",  the user is logged in and has more than 3 articles in his basket*). This even enables us to eventually replace our external tagmanager and host the entire affiliate integration right within our own git repository. If you are a developer and/or you ever worked with external tag management GUIs (or any other, less comfortable form of affiliate integration) you might know what a great relief that is. We are even able to **fully unit test our affiliate pixels, integrated within our CD pipeline**. Getting jealous? You should! ;-)    \n+\n+## Going async\n+Most modern websites don't involve a new pageload for every action. Actions like e.g. opening a layer, expanding some accordion or using the off-canvas navigation may happen anytime, asynchronously, without our DAL ever being notified. For such cases we developed the `DAL.broadcast` mechanism. It offers a simple, one-way message API that allows sending event notifications directly to the DAL. Whenever a script causes an asynchronous action that should be globally broadcasted, the `DAL.broadcast` method can be called with the specific event name and an optional information object:\n+\n+{% highlight javascript %}\n+// broadcast a client event to the DAL\n+DAL.broadcast(\"product-addtocart\", {\n+  id: \"1234567ABC\",\n+  name: \"FABIANI Jeans\",\n+  price: 19.95\n+});\n+{% endhighlight %}\n+\n+While this solved the issue of being notified about asynchronous events it introduced a new problem. We now needed to write specific controllers for any element that should fire an event. Having to write a dedicated controller for any single button actually felt quite ugly and would have caused tons of useless code. So we decided to make the tracking more declarative and introduced custom attributes that allowed to track events without additional controller logic. \n+\n+We identified three common types of client events we are usually interested in: *click/touch*, *view* and *focus*. These event types can be automatically handled and applied to the appropriate logic using the custom data-attribute syntax `data-dal-event-{type}`. This would also allow for future extensibility (thinking of *swipe*, *pinchzoom*, ...). The following examples illustrate the basic principle: \n+\n+{% highlight XML %}\n+<!-- simple click event without custom data -->\n+<button \n+  name=\"loginLayerOpen\"\n+  data-dal-event-click='{\"name\":\"layer-login-open\"}'\n+/>\n+{% endhighlight %}\n+\n+Using this *declarative tracking* it was now easily possible to apply tracking logic to elements without touching anything but the markup. For more advanced use cases it is also possible to append additional data directly to the event data, following the same type definition and argument signature we use for the `DAL.broadcast` function itself.  \n+\n+{% highlight XML %}\n+<!-- click event with custom data (a teaser id in this case) -->\n+<div \n+  class=\"my-teaser\"\n+  data-dal-event-click='{\n+    \"name\":\"teaser-click\",\n+    \"data\":{\n+      \"teaserId\":\"my-fashion-teaser\",\n+      \"campaign\":\"some/fancy/campaign\"\n+    }\n+  }' \n+/>\n+{% endhighlight %}\n+\n+## Standards and conventions ftw\n+At this point you might ask: *\"Yeah, sounds nice, but what's all the buzz about? This doesn't look like rocket science.\"* Agreed (mostly). But, that's only a small part of the cake. The majority of work went into defining conventions and standards for the various types and use cases. Which data do we have to pass to our DAL? What are the globally required fields? How do we name our pages? Which data do we need for which event and/or page? What are the more specific bits of information each vertical had to pass? \n+\n+The answers to all those questions are very subjective and closely related to the field of business. Obviously, for e-commerce you have substantially different information you want to collect, compared to a content-driven online magazine. But in any case it boils down to some key metrics you want to collect and analyze for your business. E.g. a big part of the DAL's data is passed to our RUM and BI software, which are also implemented as DAL plugins. Thus, many of our own conventions and metrics are specific to this use case. Another major part is the affiliate and basket tracking integration.  \n+\n+Luckily, I had done most of that long time before, when I initiated the project \"tagmanager integration\" for our old webshop. So I  started writing down the important KPIs and metrics based on that historical data and the trackingpixels from our tracking solution and our recommendation engine. I summed it all up in a nice table in our wiki, structured the table based on verticals and added a nice description for all keys. Then I wrote the integration stories for the vertical teams so they could integrate the right metatags and declarations in the markup. Awesome.\n+\n+Well, at least it felt awesome until I looked at the actual implementation done by the teams. The problem was that I haven't been very explicit in declaring the types (and especially the formatting) for the individual metrics. I simply defined something like *on the product detail page we need a product object with the following fields: name, price, category, ...*. Even though I also defined some examples, this still led to fairly diverse implementations. Especially the price formatting was a problem, because there were multiple interpretations about how a price should be expressed (e.g.`\"29,90€\"` vs. `29.9` which are both valid representations of the same price value).\n+\n+## Typization to the rescue\n+Since Javascript (and JSON, too) is a very loosely typed language, we needed to define abstract types (or interfaces if you're using [Typescript](http://typescriptlang.org)) that explicitly define how values have to be supplied to the DAL. That resulted in various fancy types, e.g. *DALPageData*, *DALUserData* or *DALProductData* to just name a few. Let's look at the type definition for the *DALPageData* type:\n+\n+{% highlight javascript %}\n+// @interface IDALPageData\n+DALPageData = {\n+  \n+  /**\n+   * Display name of the page, e.g. 'Homepage' (according to list in table XXX)\n+   */\n+  \"name\" : {\n+    \"type\" : \"String\",\n+    \"mandatory\" : true\n+  },\n+  \n+  /**\n+   * Unique page type for this page, e.g. 'checkout-basket'.\n+   */\n+  \"type\" : {\n+    \"type\" : \"String\",\n+    \"mandatory\" : true\n+  }\n+  \n+}\n+{% endhighlight %}\n+\n+As you can see we provide a JSON structure defining the type and some other, optional fields (e.g. *mandatory*, *deprecated*, *apiVersion* and some more). This way we ensure backwards compatibility throughout the API because the teams can safely adapt to new API versions without introducing breaking changes.\n+\n+The curious reader might ask himself: *\"Hey, what's the purpose of defining these as Javascript obejcts?\"* Well, within the metatags the data ist supplied as plain object literals. But in our test infrastructure (based on Selenium) we can now run over the entire page, take the above type definitions and compare them to the actual implementation. If there are mismatches, e.g. because a vertical didn't implement a type correctly, we raise an error and get a notification. Now, ain't that sweet?","path":"_posts/2015-02-11-abstracting-the-frontend-in-a-shared-nothing-architecture-driven-by-functional-teams.md","position":156,"original_position":156,"commit_id":"699641f3d97b6103e279c2337dadef872a2aa294","original_commit_id":"699641f3d97b6103e279c2337dadef872a2aa294","user":{"login":"manuelkiessling","id":206592,"avatar_url":"https://avatars.githubusercontent.com/u/206592?v=3","gravatar_id":"","url":"https://api.github.com/users/manuelkiessling","html_url":"https://github.com/manuelkiessling","followers_url":"https://api.github.com/users/manuelkiessling/followers","following_url":"https://api.github.com/users/manuelkiessling/following{/other_user}","gists_url":"https://api.github.com/users/manuelkiessling/gists{/gist_id}","starred_url":"https://api.github.com/users/manuelkiessling/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/manuelkiessling/subscriptions","organizations_url":"https://api.github.com/users/manuelkiessling/orgs","repos_url":"https://api.github.com/users/manuelkiessling/repos","events_url":"https://api.github.com/users/manuelkiessling/events{/privacy}","received_events_url":"https://api.github.com/users/manuelkiessling/received_events","type":"User","site_admin":false},"body":"*obejcts* -> *objects*","created_at":"2015-06-25T22:52:16Z","updated_at":"2015-06-25T22:52:16Z","html_url":"https://github.com/Galeria-Kaufhof/galeria-kaufhof.github.io/pull/8#discussion_r33313796","pull_request_url":"https://api.github.com/repos/Galeria-Kaufhof/galeria-kaufhof.github.io/pulls/8","_links":{"self":{"href":"https://api.github.com/repos/Galeria-Kaufhof/galeria-kaufhof.github.io/pulls/comments/33313796"},"html":{"href":"https://github.com/Galeria-Kaufhof/galeria-kaufhof.github.io/pull/8#discussion_r33313796"},"pull_request":{"href":"https://api.github.com/repos/Galeria-Kaufhof/galeria-kaufhof.github.io/pulls/8"}}},"pull_request":{"url":"https://api.github.com/repos/Galeria-Kaufhof/galeria-kaufhof.github.io/pulls/8","id":38518169,"html_url":"https://github.com/Galeria-Kaufhof/galeria-kaufhof.github.io/pull/8","diff_url":"https://github.com/Galeria-Kaufhof/galeria-kaufhof.github.io/pull/8.diff","patch_url":"https://github.com/Galeria-Kaufhof/galeria-kaufhof.github.io/pull/8.patch","issue_url":"https://api.github.com/repos/Galeria-Kaufhof/galeria-kaufhof.github.io/issues/8","number":8,"state":"open","locked":false,"title":"Article frontend middleware","user":{"login":"ryx","id":476417,"avatar_url":"https://avatars.githubusercontent.com/u/476417?v=3","gravatar_id":"","url":"https://api.github.com/users/ryx","html_url":"https://github.com/ryx","followers_url":"https://api.github.com/users/ryx/followers","following_url":"https://api.github.com/users/ryx/following{/other_user}","gists_url":"https://api.github.com/users/ryx/gists{/gist_id}","starred_url":"https://api.github.com/users/ryx/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/ryx/subscriptions","organizations_url":"https://api.github.com/users/ryx/orgs","repos_url":"https://api.github.com/users/ryx/repos","events_url":"https://api.github.com/users/ryx/events{/privacy}","received_events_url":"https://api.github.com/users/ryx/received_events","type":"User","site_admin":false},"body":"Hi Manuel, wie besprochen. Konstruktive Kritik sehr willkommen, gern auch direkt oder per Slack ;) .. Gruß, Rico.","created_at":"2015-06-25T07:14:19Z","updated_at":"2015-06-25T22:52:16Z","closed_at":null,"merged_at":null,"merge_commit_sha":"b7d25a213db684baee4fa3e289d82026a43b175c","assignee":null,"milestone":null,"commits_url":"https://api.github.com/repos/Galeria-Kaufhof/galeria-kaufhof.github.io/pulls/8/commits","review_comments_url":"https://api.github.com/repos/Galeria-Kaufhof/galeria-kaufhof.github.io/pulls/8/comments","review_comment_url":"https://api.github.com/repos/Galeria-Kaufhof/galeria-kaufhof.github.io/pulls/comments{/number}","comments_url":"https://api.github.com/repos/Galeria-Kaufhof/galeria-kaufhof.github.io/issues/8/comments","statuses_url":"https://api.github.com/repos/Galeria-Kaufhof/galeria-kaufhof.github.io/statuses/699641f3d97b6103e279c2337dadef872a2aa294","head":{"label":"Galeria-Kaufhof:article-frontend-middleware","ref":"article-frontend-middleware","sha":"699641f3d97b6103e279c2337dadef872a2aa294","user":{"login":"Galeria-Kaufhof","id":8912925,"avatar_url":"https://avatars.githubusercontent.com/u/8912925?v=3","gravatar_id":"","url":"https://api.github.com/users/Galeria-Kaufhof","html_url":"https://github.com/Galeria-Kaufhof","followers_url":"https://api.github.com/users/Galeria-Kaufhof/followers","following_url":"https://api.github.com/users/Galeria-Kaufhof/following{/other_user}","gists_url":"https://api.github.com/users/Galeria-Kaufhof/gists{/gist_id}","starred_url":"https://api.github.com/users/Galeria-Kaufhof/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/Galeria-Kaufhof/subscriptions","organizations_url":"https://api.github.com/users/Galeria-Kaufhof/orgs","repos_url":"https://api.github.com/users/Galeria-Kaufhof/repos","events_url":"https://api.github.com/users/Galeria-Kaufhof/events{/privacy}","received_events_url":"https://api.github.com/users/Galeria-Kaufhof/received_events","type":"Organization","site_admin":false},"repo":{"id":28637943,"name":"galeria-kaufhof.github.io","full_name":"Galeria-Kaufhof/galeria-kaufhof.github.io","owner":{"login":"Galeria-Kaufhof","id":8912925,"avatar_url":"https://avatars.githubusercontent.com/u/8912925?v=3","gravatar_id":"","url":"https://api.github.com/users/Galeria-Kaufhof","html_url":"https://github.com/Galeria-Kaufhof","followers_url":"https://api.github.com/users/Galeria-Kaufhof/followers","following_url":"https://api.github.com/users/Galeria-Kaufhof/following{/other_user}","gists_url":"https://api.github.com/users/Galeria-Kaufhof/gists{/gist_id}","starred_url":"https://api.github.com/users/Galeria-Kaufhof/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/Galeria-Kaufhof/subscriptions","organizations_url":"https://api.github.com/users/Galeria-Kaufhof/orgs","repos_url":"https://api.github.com/users/Galeria-Kaufhof/repos","events_url":"https://api.github.com/users/Galeria-Kaufhof/events{/privacy}","received_events_url":"https://api.github.com/users/Galeria-Kaufhof/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/Galeria-Kaufhof/galeria-kaufhof.github.io","description":"Our technology blog","fork":false,"url":"https://api.github.com/repos/Galeria-Kaufhof/galeria-kaufhof.github.io","forks_url":"https://api.github.com/repos/Galeria-Kaufhof/galeria-kaufhof.github.io/forks","keys_url":"https://api.github.com/repos/Galeria-Kaufhof/galeria-kaufhof.github.io/keys{/key_id}","collaborators_url":"https://api.github.com/repos/Galeria-Kaufhof/galeria-kaufhof.github.io/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/Galeria-Kaufhof/galeria-kaufhof.github.io/teams","hooks_url":"https://api.github.com/repos/Galeria-Kaufhof/galeria-kaufhof.github.io/hooks","issue_events_url":"https://api.github.com/repos/Galeria-Kaufhof/galeria-kaufhof.github.io/issues/events{/number}","events_url":"https://api.github.com/repos/Galeria-Kaufhof/galeria-kaufhof.github.io/events","assignees_url":"https://api.github.com/repos/Galeria-Kaufhof/galeria-kaufhof.github.io/assignees{/user}","branches_url":"https://api.github.com/repos/Galeria-Kaufhof/galeria-kaufhof.github.io/branches{/branch}","tags_url":"https://api.github.com/repos/Galeria-Kaufhof/galeria-kaufhof.github.io/tags","blobs_url":"https://api.github.com/repos/Galeria-Kaufhof/galeria-kaufhof.github.io/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/Galeria-Kaufhof/galeria-kaufhof.github.io/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/Galeria-Kaufhof/galeria-kaufhof.github.io/git/refs{/sha}","trees_url":"https://api.github.com/repos/Galeria-Kaufhof/galeria-kaufhof.github.io/git/trees{/sha}","statuses_url":"https://api.github.com/repos/Galeria-Kaufhof/galeria-kaufhof.github.io/statuses/{sha}","languages_url":"https://api.github.com/repos/Galeria-Kaufhof/galeria-kaufhof.github.io/languages","stargazers_url":"https://api.github.com/repos/Galeria-Kaufhof/galeria-kaufhof.github.io/stargazers","contributors_url":"https://api.github.com/repos/Galeria-Kaufhof/galeria-kaufhof.github.io/contributors","subscribers_url":"https://api.github.com/repos/Galeria-Kaufhof/galeria-kaufhof.github.io/subscribers","subscription_url":"https://api.github.com/repos/Galeria-Kaufhof/galeria-kaufhof.github.io/subscription","commits_url":"https://api.github.com/repos/Galeria-Kaufhof/galeria-kaufhof.github.io/commits{/sha}","git_commits_url":"https://api.github.com/repos/Galeria-Kaufhof/galeria-kaufhof.github.io/git/commits{/sha}","comments_url":"https://api.github.com/repos/Galeria-Kaufhof/galeria-kaufhof.github.io/comments{/number}","issue_comment_url":"https://api.github.com/repos/Galeria-Kaufhof/galeria-kaufhof.github.io/issues/comments{/number}","contents_url":"https://api.github.com/repos/Galeria-Kaufhof/galeria-kaufhof.github.io/contents/{+path}","compare_url":"https://api.github.com/repos/Galeria-Kaufhof/galeria-kaufhof.github.io/compare/{base}...{head}","merges_url":"https://api.github.com/repos/Galeria-Kaufhof/galeria-kaufhof.github.io/merges","archive_url":"https://api.github.com/repos/Galeria-Kaufhof/galeria-kaufhof.github.io/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/Galeria-Kaufhof/galeria-kaufhof.github.io/downloads","issues_url":"https://api.github.com/repos/Galeria-Kaufhof/galeria-kaufhof.github.io/issues{/number}","pulls_url":"https://api.github.com/repos/Galeria-Kaufhof/galeria-kaufhof.github.io/pulls{/number}","milestones_url":"https://api.github.com/repos/Galeria-Kaufhof/galeria-kaufhof.github.io/milestones{/number}","notifications_url":"https://api.github.com/repos/Galeria-Kaufhof/galeria-kaufhof.github.io/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/Galeria-Kaufhof/galeria-kaufhof.github.io/labels{/name}","releases_url":"https://api.github.com/repos/Galeria-Kaufhof/galeria-kaufhof.github.io/releases{/id}","created_at":"2014-12-30T16:43:37Z","updated_at":"2015-03-02T07:33:47Z","pushed_at":"2015-06-25T07:14:19Z","git_url":"git://github.com/Galeria-Kaufhof/galeria-kaufhof.github.io.git","ssh_url":"git@github.com:Galeria-Kaufhof/galeria-kaufhof.github.io.git","clone_url":"https://github.com/Galeria-Kaufhof/galeria-kaufhof.github.io.git","svn_url":"https://github.com/Galeria-Kaufhof/galeria-kaufhof.github.io","homepage":"https://galeria-kaufhof.github.io","size":6220,"stargazers_count":3,"watchers_count":3,"language":"HTML","has_issues":true,"has_downloads":true,"has_wiki":false,"has_pages":true,"forks_count":0,"mirror_url":null,"open_issues_count":6,"forks":0,"open_issues":6,"watchers":3,"default_branch":"master"}},"base":{"label":"Galeria-Kaufhof:master","ref":"master","sha":"b18c977e0fc11711eb78b1e8e5f8210a6592cd56","user":{"login":"Galeria-Kaufhof","id":8912925,"avatar_url":"https://avatars.githubusercontent.com/u/8912925?v=3","gravatar_id":"","url":"https://api.github.com/users/Galeria-Kaufhof","html_url":"https://github.com/Galeria-Kaufhof","followers_url":"https://api.github.com/users/Galeria-Kaufhof/followers","following_url":"https://api.github.com/users/Galeria-Kaufhof/following{/other_user}","gists_url":"https://api.github.com/users/Galeria-Kaufhof/gists{/gist_id}","starred_url":"https://api.github.com/users/Galeria-Kaufhof/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/Galeria-Kaufhof/subscriptions","organizations_url":"https://api.github.com/users/Galeria-Kaufhof/orgs","repos_url":"https://api.github.com/users/Galeria-Kaufhof/repos","events_url":"https://api.github.com/users/Galeria-Kaufhof/events{/privacy}","received_events_url":"https://api.github.com/users/Galeria-Kaufhof/received_events","type":"Organization","site_admin":false},"repo":{"id":28637943,"name":"galeria-kaufhof.github.io","full_name":"Galeria-Kaufhof/galeria-kaufhof.github.io","owner":{"login":"Galeria-Kaufhof","id":8912925,"avatar_url":"https://avatars.githubusercontent.com/u/8912925?v=3","gravatar_id":"","url":"https://api.github.com/users/Galeria-Kaufhof","html_url":"https://github.com/Galeria-Kaufhof","followers_url":"https://api.github.com/users/Galeria-Kaufhof/followers","following_url":"https://api.github.com/users/Galeria-Kaufhof/following{/other_user}","gists_url":"https://api.github.com/users/Galeria-Kaufhof/gists{/gist_id}","starred_url":"https://api.github.com/users/Galeria-Kaufhof/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/Galeria-Kaufhof/subscriptions","organizations_url":"https://api.github.com/users/Galeria-Kaufhof/orgs","repos_url":"https://api.github.com/users/Galeria-Kaufhof/repos","events_url":"https://api.github.com/users/Galeria-Kaufhof/events{/privacy}","received_events_url":"https://api.github.com/users/Galeria-Kaufhof/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/Galeria-Kaufhof/galeria-kaufhof.github.io","description":"Our technology blog","fork":false,"url":"https://api.github.com/repos/Galeria-Kaufhof/galeria-kaufhof.github.io","forks_url":"https://api.github.com/repos/Galeria-Kaufhof/galeria-kaufhof.github.io/forks","keys_url":"https://api.github.com/repos/Galeria-Kaufhof/galeria-kaufhof.github.io/keys{/key_id}","collaborators_url":"https://api.github.com/repos/Galeria-Kaufhof/galeria-kaufhof.github.io/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/Galeria-Kaufhof/galeria-kaufhof.github.io/teams","hooks_url":"https://api.github.com/repos/Galeria-Kaufhof/galeria-kaufhof.github.io/hooks","issue_events_url":"https://api.github.com/repos/Galeria-Kaufhof/galeria-kaufhof.github.io/issues/events{/number}","events_url":"https://api.github.com/repos/Galeria-Kaufhof/galeria-kaufhof.github.io/events","assignees_url":"https://api.github.com/repos/Galeria-Kaufhof/galeria-kaufhof.github.io/assignees{/user}","branches_url":"https://api.github.com/repos/Galeria-Kaufhof/galeria-kaufhof.github.io/branches{/branch}","tags_url":"https://api.github.com/repos/Galeria-Kaufhof/galeria-kaufhof.github.io/tags","blobs_url":"https://api.github.com/repos/Galeria-Kaufhof/galeria-kaufhof.github.io/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/Galeria-Kaufhof/galeria-kaufhof.github.io/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/Galeria-Kaufhof/galeria-kaufhof.github.io/git/refs{/sha}","trees_url":"https://api.github.com/repos/Galeria-Kaufhof/galeria-kaufhof.github.io/git/trees{/sha}","statuses_url":"https://api.github.com/repos/Galeria-Kaufhof/galeria-kaufhof.github.io/statuses/{sha}","languages_url":"https://api.github.com/repos/Galeria-Kaufhof/galeria-kaufhof.github.io/languages","stargazers_url":"https://api.github.com/repos/Galeria-Kaufhof/galeria-kaufhof.github.io/stargazers","contributors_url":"https://api.github.com/repos/Galeria-Kaufhof/galeria-kaufhof.github.io/contributors","subscribers_url":"https://api.github.com/repos/Galeria-Kaufhof/galeria-kaufhof.github.io/subscribers","subscription_url":"https://api.github.com/repos/Galeria-Kaufhof/galeria-kaufhof.github.io/subscription","commits_url":"https://api.github.com/repos/Galeria-Kaufhof/galeria-kaufhof.github.io/commits{/sha}","git_commits_url":"https://api.github.com/repos/Galeria-Kaufhof/galeria-kaufhof.github.io/git/commits{/sha}","comments_url":"https://api.github.com/repos/Galeria-Kaufhof/galeria-kaufhof.github.io/comments{/number}","issue_comment_url":"https://api.github.com/repos/Galeria-Kaufhof/galeria-kaufhof.github.io/issues/comments{/number}","contents_url":"https://api.github.com/repos/Galeria-Kaufhof/galeria-kaufhof.github.io/contents/{+path}","compare_url":"https://api.github.com/repos/Galeria-Kaufhof/galeria-kaufhof.github.io/compare/{base}...{head}","merges_url":"https://api.github.com/repos/Galeria-Kaufhof/galeria-kaufhof.github.io/merges","archive_url":"https://api.github.com/repos/Galeria-Kaufhof/galeria-kaufhof.github.io/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/Galeria-Kaufhof/galeria-kaufhof.github.io/downloads","issues_url":"https://api.github.com/repos/Galeria-Kaufhof/galeria-kaufhof.github.io/issues{/number}","pulls_url":"https://api.github.com/repos/Galeria-Kaufhof/galeria-kaufhof.github.io/pulls{/number}","milestones_url":"https://api.github.com/repos/Galeria-Kaufhof/galeria-kaufhof.github.io/milestones{/number}","notifications_url":"https://api.github.com/repos/Galeria-Kaufhof/galeria-kaufhof.github.io/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/Galeria-Kaufhof/galeria-kaufhof.github.io/labels{/name}","releases_url":"https://api.github.com/repos/Galeria-Kaufhof/galeria-kaufhof.github.io/releases{/id}","created_at":"2014-12-30T16:43:37Z","updated_at":"2015-03-02T07:33:47Z","pushed_at":"2015-06-25T07:14:19Z","git_url":"git://github.com/Galeria-Kaufhof/galeria-kaufhof.github.io.git","ssh_url":"git@github.com:Galeria-Kaufhof/galeria-kaufhof.github.io.git","clone_url":"https://github.com/Galeria-Kaufhof/galeria-kaufhof.github.io.git","svn_url":"https://github.com/Galeria-Kaufhof/galeria-kaufhof.github.io","homepage":"https://galeria-kaufhof.github.io","size":6220,"stargazers_count":3,"watchers_count":3,"language":"HTML","has_issues":true,"has_downloads":true,"has_wiki":false,"has_pages":true,"forks_count":0,"mirror_url":null,"open_issues_count":6,"forks":0,"open_issues":6,"watchers":3,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/Galeria-Kaufhof/galeria-kaufhof.github.io/pulls/8"},"html":{"href":"https://github.com/Galeria-Kaufhof/galeria-kaufhof.github.io/pull/8"},"issue":{"href":"https://api.github.com/repos/Galeria-Kaufhof/galeria-kaufhof.github.io/issues/8"},"comments":{"href":"https://api.github.com/repos/Galeria-Kaufhof/galeria-kaufhof.github.io/issues/8/comments"},"review_comments":{"href":"https://api.github.com/repos/Galeria-Kaufhof/galeria-kaufhof.github.io/pulls/8/comments"},"review_comment":{"href":"https://api.github.com/repos/Galeria-Kaufhof/galeria-kaufhof.github.io/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/Galeria-Kaufhof/galeria-kaufhof.github.io/pulls/8/commits"},"statuses":{"href":"https://api.github.com/repos/Galeria-Kaufhof/galeria-kaufhof.github.io/statuses/699641f3d97b6103e279c2337dadef872a2aa294"}}}}