{"action":"created","comment":{"url":"https://api.github.com/repos/docker/labs/pulls/comments/99042360","pull_request_review_id":19700531,"id":99042360,"diff_hunk":"@@ -0,0 +1,183 @@\n+# Part 4 - Improving Performance with Asynchronous Messaging\n+\n+Modernizing apps can be a significant amount of work. You can break a monolithic app into microservices along the lines of bounded contexts, re-platform all the services to use .NET Core and run them in Nano Server containers. There's a lot to gain from doing that, and the Docker platform is an enabler for that approach, but it's a rebuild project which needs a lot of investment.  Docker also supports a more targeted, feature-driven modernization, where you take specific features of your app that need improving, and redesign them to take advantage of Docker - without an extensive rebuild.\n+\n+We're going to look at one feature improvement in this part, addressing performance and scalability. In version 1 of the app the sign-up form makes a connection to the database and executes synchronous database queries. That approach doesn't scale. If there's a spike in traffic to our site we can run more web containers to spread the load, but we'd hit a bottleneck on the number of open connections the database can handle. We'd have to scale the database toom because the web tier is tightly coupled to the data tier.\n+\n+For this scenario, we can easily address that by making the sign up feature asynchronous. Instead of persisting to the database directly, the sign-up form will publish an event to a message queue, and a handler listening to that event makes the database call:\n+\n+![v2 architecture](img/v2-arch.png)\n+\n+That decouples the web layer from the data layer and means we can scale to meet demand just by adding more web containers. At times of high load the message queue will hold onto the events until the handler is ready to action them. There may be a delay between users clicking the button and their data being persisted, but the delay is happening offline - the user will see the thank-you page almost instantly, no matter how much traffic is hitting the site.\n+\n+## Changing the App to Use Asynchronous Messaging\n+\n+In the `v2-src` folder there's a new version of the solution which uses messaging to publish an event when a prospect signs up, rather than writing to the database directly. The main change is in the [SignUp code-behind](v2-src/ProductLaunch/ProductLaunch.Web/SignUp.aspx.cs) for the webform. In version 1 the `btnGo_Click` handler used this code, to insert the prospect details into the database:\n+\n+```\n+using (var context = new ProductLaunchContext())\n+{\n+    //reload child objects:\n+    prospect.Country = context.Countries.Single(x => x.CountryCode == prospect.Country.CountryCode);\n+    prospect.Role = context.Roles.Single(x => x.RoleCode == prospect.Role.RoleCode);\n+\n+    context.Prospects.Add(prospect);\n+    context.SaveChanges();\n+}\n+```\n+\n+In version 2, that's been replaced with this code to publish an event message:\n+\n+```\n+var eventMessage = new ProspectSignedUpEvent\n+{\n+    Prospect = prospect,\n+    SignedUpAt = DateTime.UtcNow\n+};\n+\n+MessageQueue.Publish(eventMessage);\n+```\n+\n+The `ProspectSignedUpEvent` object contains the original `Prospect` object, populated from the webform input. The `MessageQueue` class is just a wrapper to abstract the type of message queue. In this lab I'm using [NATS](https://nats.io), a high-performance, low-latency, cross-platform and open-source message server. NATS is available as an [official image](https://hub.docker.com/_/nats/) on Docker Hub, which means its a curated image that you can rely on for quality. Publishing a Message to NATS means multiple subscribers can listen for the event, and we start to bring [event-driven architecture](https://msdn.microsoft.com/en-us/library/dd129913.aspx) into our application - just for the one feature that needs it, without a full rewrite.\n+\n+## Changing App Configuration to use Environment Variables\n+\n+One other thing has changed in the WebForms app. Instead of using `Web.config` for configuration values which may change between environments, we now use environment variables. Lightweight modern app frameworks like [NodeJS](https://nodejs.org/en/) and [.NET Core](www.microsoft.com/net/core) use environment variables for configuration settings, because they're available in pretty much any host platform - Windows, Linux and PaaS platforms in the cloud. We can do the same in full .NET apps, and moving from config files baked into the deployment package to environment variables set by the platform makes our app more portable.\n+\n+In the Entity Framework [ProductLaunchContext](v2-src/ProductLaunch/ProductLaunch.Model/ProductLaunchContext.cs) class we now load the database connection string from an environment variable, using a simple [Config](v2-src/ProductLaunch/ProductLaunch.Model/Config.cs) class which just reads the value by its key:\n+\n+```\n+var value = Environment.GetEnvironmentVariable(variable, EnvironmentVariableTarget.Machine);\n+```\n+\n+To configure the database, we just need to set the connection string as an environment variable named `DB_CONNECTION_STRING`. A similar [Config](v2-src/ProductLaunch/ProductLaunch.Messaging/Config.cs) class in the message queue project gets the URL for the NATS host from an environment variable named `MESSAGE_QUEUE_URL`. The Docker platform has first-class support for environment variables. They can be created with a default value in a Docker image, and containers can be run with specific values.\n+\n+In the [Dockerfile](v2-src/docker/web/Dockerfile) for the web app, the default value for the message queue URL is specified as an environment variable using the `ENV` instruction:\n+\n+```\n+ENV MESSAGE_QUEUE_URL=\"nats://message-queue:4222\"\n+```\n+\n+Any container we start from that image will have the same value for the URL, unless we specifically override it. For the message queue, that's what we want because we'll control the name of the message queue container and we can be confident it will always match the expected URL. For the database connection string we don't set a default value, because it needs user crtdentials which are likely to change between environments, so we will specify that at the container level.\n+\n+## Building a .NET Message handler\n+\n+Now the web app is built and configured to use messaging, we need another component in the solution to listen for events and save data to the database. Message handlers are typically simple components that do a single job - they listen for a specific type of message and act on it.\n+\n+In the version 2 source folder there's a message hanbdler project. It's a .NET console app with all the code in the [Program](v2-src/ProductLaunch/ProductLaunch.MessageHandlers.SaveProspect/Program.cs) class. It connects to NATS using the same messaging project, and listens for `ProspectSignedUpEvent` messages. For each message it receives, the handler extracts the prospect details from the message and saves them to the database:\n+\n+```\n+var prospect = eventMessage.Prospect;\n+using (var context = new ProductLaunchContext())\n+{\n+    //reload child objects:\n+    prospect.Country = context.Countries.Single(x => x.CountryCode == prospect.Country.CountryCode);\n+    prospect.Role = context.Roles.Single(x => x.RoleCode == prospect.Role.RoleCode);\n+\n+    context.Prospects.Add(prospect);\n+    context.SaveChanges();\n+}\n+```\n+\n+That's the exact same code that was in the web form in version 1. This is a common pattern that applies for any features which are resource-bound and need to scale well. You extract the functionality from the synchronous implementation, and publish a message instead. Then you move the extracted code to a message handler - this is a simple example, but if you have a complex function with multiple external dependencies, the practice is exactly the same.\n+\n+In this case we'll be running a single instance of the message handler, but for performance-critical functions you can run multiple instances and they will share the work from the queue. The message queue acts as a buffer, smoothing out any peaks in demand form the website and presenting a consistent flow of work to the message handler.\n+\n+The messaga handler will run in a Docker container too. The [Dockerfile](v2-src/docker/save-handler/Dockerfile) is very simple. .NET is already installed in the `microsoft/windowsservercore` base image, so in the Dockerfile we just configure the DNS cache, set the default message queue URL in an environment variable and copy in the compiled console app. In this example there's no `HEALTHCHECK`, but it would be good practice to add an HTTP endpoint to the console app which reported the app status, so we could add a health check to this component.\n+\n+\n+## Co-ordinating Multiple Containers in Docker\n+\n+Our solution has evolved in version 2, and we now have a distributed solution running across four containers. There are dependencies between those containers. The database and the message queue need to be running for the web application to run correctly, and the message handler needs to be running for the full feature set to work. The containers all need to be on the same Docker network so they can communicate, and the dependent containers need to have the expected names so Docker can resolve them by hostname.\n+\n+We could manage those dependencies manually by starting containers in the correct order, or we could automate all the `docker run` commands in a PowerShell script. A better option is to use another part of the Docker Platform, [Docker Compose](https://docs.docker.com/compose/). Compose is a tool for capturing complex distributed solutions in a single, executable script file. Just as the Dockerfile replaces the deployment document for a single component, the compose file replaces the deployment document for a whole solution.\n+\n+This is the definition of the web application in the [version 2 Docker Compose file](v2-src/docker-compose.yml):\n+\n+```\n+  product-launch-web:\n+    image: modernize-aspnet-web:v2\n+    ports:\n+      - \"80:80\"\n+    environment:\n+      - DB_CONNECTION_STRING=Server=product-launch-db;Database=ProductLaunch;User Id=sa;Password=d0ck3r_Labs!;\n+    depends_on:\n+      - product-launch-db\n+      - message-queue\n+    networks:\n+      - app-net\n+```\n+\n+Docker Compose lets you capture the setup of each service using familiar terminology from `docker run`. We specify the image to create containers from, publish port 80, and specify the database connection string in an environment variable (if you think having the credentials in plain text isn't good, there are [other ways](https://docs.docker.com/compose/env-file/) of dealing with [secrets in Docker](https://docs.docker.com/engine/swarm/secrets/).\n+\n+There is extra functionality in Docker Compose too. The `depends_on` attribute lets you explicitly define dependencies between services. In this case the web service is dependent on the database and message queue services - Compose will ensure those services are running before it starts the web service (\"service\" is Compose terminology - a service definition is implemented by running one or more containers).\n+\n+In the version 2 code, the [compilation build script](v2-src/ProductLaunch/build.ps1) adds a new step to build the message handler console app. The ASP.NET build agent we put together in [Part 1](part-1.md) can also build the console app, so the [packaging build script](v2-src/build.ps1) uses the same build agent for the website and the console app. There's a new `docker build` step to create the image for the console app, and that's all we need to build and package the full solution.\n+\n+## Running the Version 2 App with Docker Compose\n+\n+We can clean up the previous version of the app by killing and then removing all the containers. **WARNING: this will remove all of your containers**.\n+\n+```\n+docker kill $(docker ps -a -q)\n+docker rm $(docker ps -a -q)\n+```\n+\n+Docker Compose can be used for running solutions as well as defining them. From the `v2-src` directory we can build and run the application with two commands:\n+\n+```\n+.\\build.ps1\n+docker-compose up -d\n+```\n+\n+> If you're using [Docker for Windows](https://docs.docker.com/docker-for-windows/) on Windows 10, then Docker Compose is already installed as part of the package. On Windows Server 2016 you can download `docker-compose.exe` from the [GitHub release page](https://github.com/docker/compose/releases). \n+\n+Under the hood, Docker Compose uses the API from the Docker Engine to run containers. It's a wrapper around the Docker Engine rather than a separate component. When you run this for the first time, Docker will pull down the NATS image (which is a very small layer on top of the Windows Server Core image), and then start all the containers. `docker ps` will show you all the containers, and you can fetch the IP address of the new web container with:\n+\n+```\n+docker inspect --format '{{ .NetworkSettings.Networks.nat.IPAddress }}' v2src_product-launch-web_1\n+```\n+\n+Now you can browse to the website and enter a new prospect, and the behavior for the user is exactly the same. But the web layer and data layer are decopupled now, so to support thousands of concurrent users, we can scale up just by running more containers on more hosts in a [Docker Swarm](https://www.docker.com/products/docker-swarm).\n+\n+To verify that the event message is being published and handled, we can look at the logs from the .NET console app:\n+\n+```\n+> docker logs v2src_save-prospect-handler_1\n+Connecting to message queue url: nats://message-queue:4222\n+Listening on subject: events.prospect.signedup\n+Received message, subject: events.prospect.signedup\n+Saving new prospect, signed up at: 2/1/2017 8:48:34 PM; event ID: f11fba28-45ee-4476-a9a8-24b3c4689240\n+Prospect saved. Prospect ID: 1; event ID: f11fba28-45ee-4476-a9a8-24b3c4689240\n+```\n+\n+That output is from the `Console.WriteLine()` statements in the code, which Docker is able to record and show. And we can still run a SQL command in the database container:\n+\n+```\n+docker exec v2src_product-launch-db_1 `\n+ powershell -Command `\n+  \"Invoke-SqlCmd -Query 'SELECT * FROM Prospects' -Database ProductLaunch\"\n+```\n+\n+That will return anhy data you've added since you ran version 2:\n+\n+```\n+ProspectId          : 1\n+FirstName           : Solomon","path":"windows/modernize-traditional-apps/modernize-aspnet/part-4.md","position":166,"original_position":166,"commit_id":"93528866a7429e1bc68209ca4d146e513d8fd3f1","original_commit_id":"93528866a7429e1bc68209ca4d146e513d8fd3f1","user":{"login":"ManoMarks","id":113855,"avatar_url":"https://avatars.githubusercontent.com/u/113855?v=3","gravatar_id":"","url":"https://api.github.com/users/ManoMarks","html_url":"https://github.com/ManoMarks","followers_url":"https://api.github.com/users/ManoMarks/followers","following_url":"https://api.github.com/users/ManoMarks/following{/other_user}","gists_url":"https://api.github.com/users/ManoMarks/gists{/gist_id}","starred_url":"https://api.github.com/users/ManoMarks/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/ManoMarks/subscriptions","organizations_url":"https://api.github.com/users/ManoMarks/orgs","repos_url":"https://api.github.com/users/ManoMarks/repos","events_url":"https://api.github.com/users/ManoMarks/events{/privacy}","received_events_url":"https://api.github.com/users/ManoMarks/received_events","type":"User","site_admin":false},"body":"Let's use Gordon instead of Solomon, don't encourage people to write to Solomon :-)","created_at":"2017-02-02T02:18:48Z","updated_at":"2017-02-02T02:28:28Z","html_url":"https://github.com/docker/labs/pull/164#discussion_r99042360","pull_request_url":"https://api.github.com/repos/docker/labs/pulls/164","_links":{"self":{"href":"https://api.github.com/repos/docker/labs/pulls/comments/99042360"},"html":{"href":"https://github.com/docker/labs/pull/164#discussion_r99042360"},"pull_request":{"href":"https://api.github.com/repos/docker/labs/pulls/164"}}},"pull_request":{"url":"https://api.github.com/repos/docker/labs/pulls/164","id":104255048,"html_url":"https://github.com/docker/labs/pull/164","diff_url":"https://github.com/docker/labs/pull/164.diff","patch_url":"https://github.com/docker/labs/pull/164.patch","issue_url":"https://api.github.com/repos/docker/labs/issues/164","number":164,"state":"open","locked":false,"title":"Add Modernize ASP.NET Apps lab","user":{"login":"sixeyed","id":157413,"avatar_url":"https://avatars.githubusercontent.com/u/157413?v=3","gravatar_id":"","url":"https://api.github.com/users/sixeyed","html_url":"https://github.com/sixeyed","followers_url":"https://api.github.com/users/sixeyed/followers","following_url":"https://api.github.com/users/sixeyed/following{/other_user}","gists_url":"https://api.github.com/users/sixeyed/gists{/gist_id}","starred_url":"https://api.github.com/users/sixeyed/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/sixeyed/subscriptions","organizations_url":"https://api.github.com/users/sixeyed/orgs","repos_url":"https://api.github.com/users/sixeyed/repos","events_url":"https://api.github.com/users/sixeyed/events{/privacy}","received_events_url":"https://api.github.com/users/sixeyed/received_events","type":"User","site_admin":false},"body":"","created_at":"2017-02-02T00:48:37Z","updated_at":"2017-02-02T02:28:28Z","closed_at":null,"merged_at":null,"merge_commit_sha":"a9d59bf8758a9ceb286cdddf1e5942a757541169","assignee":null,"assignees":[],"milestone":null,"commits_url":"https://api.github.com/repos/docker/labs/pulls/164/commits","review_comments_url":"https://api.github.com/repos/docker/labs/pulls/164/comments","review_comment_url":"https://api.github.com/repos/docker/labs/pulls/comments{/number}","comments_url":"https://api.github.com/repos/docker/labs/issues/164/comments","statuses_url":"https://api.github.com/repos/docker/labs/statuses/93528866a7429e1bc68209ca4d146e513d8fd3f1","head":{"label":"sixeyed:master","ref":"master","sha":"93528866a7429e1bc68209ca4d146e513d8fd3f1","user":{"login":"sixeyed","id":157413,"avatar_url":"https://avatars.githubusercontent.com/u/157413?v=3","gravatar_id":"","url":"https://api.github.com/users/sixeyed","html_url":"https://github.com/sixeyed","followers_url":"https://api.github.com/users/sixeyed/followers","following_url":"https://api.github.com/users/sixeyed/following{/other_user}","gists_url":"https://api.github.com/users/sixeyed/gists{/gist_id}","starred_url":"https://api.github.com/users/sixeyed/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/sixeyed/subscriptions","organizations_url":"https://api.github.com/users/sixeyed/orgs","repos_url":"https://api.github.com/users/sixeyed/repos","events_url":"https://api.github.com/users/sixeyed/events{/privacy}","received_events_url":"https://api.github.com/users/sixeyed/received_events","type":"User","site_admin":false},"repo":{"id":80676774,"name":"labs","full_name":"sixeyed/labs","owner":{"login":"sixeyed","id":157413,"avatar_url":"https://avatars.githubusercontent.com/u/157413?v=3","gravatar_id":"","url":"https://api.github.com/users/sixeyed","html_url":"https://github.com/sixeyed","followers_url":"https://api.github.com/users/sixeyed/followers","following_url":"https://api.github.com/users/sixeyed/following{/other_user}","gists_url":"https://api.github.com/users/sixeyed/gists{/gist_id}","starred_url":"https://api.github.com/users/sixeyed/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/sixeyed/subscriptions","organizations_url":"https://api.github.com/users/sixeyed/orgs","repos_url":"https://api.github.com/users/sixeyed/repos","events_url":"https://api.github.com/users/sixeyed/events{/privacy}","received_events_url":"https://api.github.com/users/sixeyed/received_events","type":"User","site_admin":false},"private":false,"html_url":"https://github.com/sixeyed/labs","description":"This is a collection of tutorials for learning how to use Docker with various tools. Contributions welcome.","fork":true,"url":"https://api.github.com/repos/sixeyed/labs","forks_url":"https://api.github.com/repos/sixeyed/labs/forks","keys_url":"https://api.github.com/repos/sixeyed/labs/keys{/key_id}","collaborators_url":"https://api.github.com/repos/sixeyed/labs/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/sixeyed/labs/teams","hooks_url":"https://api.github.com/repos/sixeyed/labs/hooks","issue_events_url":"https://api.github.com/repos/sixeyed/labs/issues/events{/number}","events_url":"https://api.github.com/repos/sixeyed/labs/events","assignees_url":"https://api.github.com/repos/sixeyed/labs/assignees{/user}","branches_url":"https://api.github.com/repos/sixeyed/labs/branches{/branch}","tags_url":"https://api.github.com/repos/sixeyed/labs/tags","blobs_url":"https://api.github.com/repos/sixeyed/labs/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/sixeyed/labs/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/sixeyed/labs/git/refs{/sha}","trees_url":"https://api.github.com/repos/sixeyed/labs/git/trees{/sha}","statuses_url":"https://api.github.com/repos/sixeyed/labs/statuses/{sha}","languages_url":"https://api.github.com/repos/sixeyed/labs/languages","stargazers_url":"https://api.github.com/repos/sixeyed/labs/stargazers","contributors_url":"https://api.github.com/repos/sixeyed/labs/contributors","subscribers_url":"https://api.github.com/repos/sixeyed/labs/subscribers","subscription_url":"https://api.github.com/repos/sixeyed/labs/subscription","commits_url":"https://api.github.com/repos/sixeyed/labs/commits{/sha}","git_commits_url":"https://api.github.com/repos/sixeyed/labs/git/commits{/sha}","comments_url":"https://api.github.com/repos/sixeyed/labs/comments{/number}","issue_comment_url":"https://api.github.com/repos/sixeyed/labs/issues/comments{/number}","contents_url":"https://api.github.com/repos/sixeyed/labs/contents/{+path}","compare_url":"https://api.github.com/repos/sixeyed/labs/compare/{base}...{head}","merges_url":"https://api.github.com/repos/sixeyed/labs/merges","archive_url":"https://api.github.com/repos/sixeyed/labs/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/sixeyed/labs/downloads","issues_url":"https://api.github.com/repos/sixeyed/labs/issues{/number}","pulls_url":"https://api.github.com/repos/sixeyed/labs/pulls{/number}","milestones_url":"https://api.github.com/repos/sixeyed/labs/milestones{/number}","notifications_url":"https://api.github.com/repos/sixeyed/labs/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/sixeyed/labs/labels{/name}","releases_url":"https://api.github.com/repos/sixeyed/labs/releases{/id}","deployments_url":"https://api.github.com/repos/sixeyed/labs/deployments","created_at":"2017-02-01T23:51:29Z","updated_at":"2017-02-01T23:51:36Z","pushed_at":"2017-02-02T02:08:22Z","git_url":"git://github.com/sixeyed/labs.git","ssh_url":"git@github.com:sixeyed/labs.git","clone_url":"https://github.com/sixeyed/labs.git","svn_url":"https://github.com/sixeyed/labs","homepage":"","size":134645,"stargazers_count":0,"watchers_count":0,"language":"PHP","has_issues":false,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":0,"mirror_url":null,"open_issues_count":0,"forks":0,"open_issues":0,"watchers":0,"default_branch":"master"}},"base":{"label":"docker:master","ref":"master","sha":"0516732e06a0b2d6f189dd37846971670070412e","user":{"login":"docker","id":5429470,"avatar_url":"https://avatars.githubusercontent.com/u/5429470?v=3","gravatar_id":"","url":"https://api.github.com/users/docker","html_url":"https://github.com/docker","followers_url":"https://api.github.com/users/docker/followers","following_url":"https://api.github.com/users/docker/following{/other_user}","gists_url":"https://api.github.com/users/docker/gists{/gist_id}","starred_url":"https://api.github.com/users/docker/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/docker/subscriptions","organizations_url":"https://api.github.com/users/docker/orgs","repos_url":"https://api.github.com/users/docker/repos","events_url":"https://api.github.com/users/docker/events{/privacy}","received_events_url":"https://api.github.com/users/docker/received_events","type":"Organization","site_admin":false},"repo":{"id":56728437,"name":"labs","full_name":"docker/labs","owner":{"login":"docker","id":5429470,"avatar_url":"https://avatars.githubusercontent.com/u/5429470?v=3","gravatar_id":"","url":"https://api.github.com/users/docker","html_url":"https://github.com/docker","followers_url":"https://api.github.com/users/docker/followers","following_url":"https://api.github.com/users/docker/following{/other_user}","gists_url":"https://api.github.com/users/docker/gists{/gist_id}","starred_url":"https://api.github.com/users/docker/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/docker/subscriptions","organizations_url":"https://api.github.com/users/docker/orgs","repos_url":"https://api.github.com/users/docker/repos","events_url":"https://api.github.com/users/docker/events{/privacy}","received_events_url":"https://api.github.com/users/docker/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/docker/labs","description":"This is a collection of tutorials for learning how to use Docker with various tools. Contributions welcome.","fork":false,"url":"https://api.github.com/repos/docker/labs","forks_url":"https://api.github.com/repos/docker/labs/forks","keys_url":"https://api.github.com/repos/docker/labs/keys{/key_id}","collaborators_url":"https://api.github.com/repos/docker/labs/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/docker/labs/teams","hooks_url":"https://api.github.com/repos/docker/labs/hooks","issue_events_url":"https://api.github.com/repos/docker/labs/issues/events{/number}","events_url":"https://api.github.com/repos/docker/labs/events","assignees_url":"https://api.github.com/repos/docker/labs/assignees{/user}","branches_url":"https://api.github.com/repos/docker/labs/branches{/branch}","tags_url":"https://api.github.com/repos/docker/labs/tags","blobs_url":"https://api.github.com/repos/docker/labs/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/docker/labs/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/docker/labs/git/refs{/sha}","trees_url":"https://api.github.com/repos/docker/labs/git/trees{/sha}","statuses_url":"https://api.github.com/repos/docker/labs/statuses/{sha}","languages_url":"https://api.github.com/repos/docker/labs/languages","stargazers_url":"https://api.github.com/repos/docker/labs/stargazers","contributors_url":"https://api.github.com/repos/docker/labs/contributors","subscribers_url":"https://api.github.com/repos/docker/labs/subscribers","subscription_url":"https://api.github.com/repos/docker/labs/subscription","commits_url":"https://api.github.com/repos/docker/labs/commits{/sha}","git_commits_url":"https://api.github.com/repos/docker/labs/git/commits{/sha}","comments_url":"https://api.github.com/repos/docker/labs/comments{/number}","issue_comment_url":"https://api.github.com/repos/docker/labs/issues/comments{/number}","contents_url":"https://api.github.com/repos/docker/labs/contents/{+path}","compare_url":"https://api.github.com/repos/docker/labs/compare/{base}...{head}","merges_url":"https://api.github.com/repos/docker/labs/merges","archive_url":"https://api.github.com/repos/docker/labs/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/docker/labs/downloads","issues_url":"https://api.github.com/repos/docker/labs/issues{/number}","pulls_url":"https://api.github.com/repos/docker/labs/pulls{/number}","milestones_url":"https://api.github.com/repos/docker/labs/milestones{/number}","notifications_url":"https://api.github.com/repos/docker/labs/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/docker/labs/labels{/name}","releases_url":"https://api.github.com/repos/docker/labs/releases{/id}","deployments_url":"https://api.github.com/repos/docker/labs/deployments","created_at":"2016-04-20T23:52:34Z","updated_at":"2017-02-02T01:07:28Z","pushed_at":"2017-02-02T02:08:23Z","git_url":"git://github.com/docker/labs.git","ssh_url":"git@github.com:docker/labs.git","clone_url":"https://github.com/docker/labs.git","svn_url":"https://github.com/docker/labs","homepage":"","size":132656,"stargazers_count":1763,"watchers_count":1763,"language":"PHP","has_issues":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":405,"mirror_url":null,"open_issues_count":8,"forks":405,"open_issues":8,"watchers":1763,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/docker/labs/pulls/164"},"html":{"href":"https://github.com/docker/labs/pull/164"},"issue":{"href":"https://api.github.com/repos/docker/labs/issues/164"},"comments":{"href":"https://api.github.com/repos/docker/labs/issues/164/comments"},"review_comments":{"href":"https://api.github.com/repos/docker/labs/pulls/164/comments"},"review_comment":{"href":"https://api.github.com/repos/docker/labs/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/docker/labs/pulls/164/commits"},"statuses":{"href":"https://api.github.com/repos/docker/labs/statuses/93528866a7429e1bc68209ca4d146e513d8fd3f1"}}}}
{"actor":{"display_login":"ManoMarks"}}