<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>HiDef Software</title>
        <description>HiDef Software provides bold technical consulting on business and distributed systems development.</description>
        <link>https://www.hidefsoftware.co.uk/</link>
        <atom:link href="https://www.hidefsoftware.co.uk/feed.xml" rel="self" type="application/rss+xml"/>
        <pubDate>Sun, 15 May 2022 21:51:04 +0000</pubDate>
        <lastBuildDate>Sun, 15 May 2022 21:51:04 +0000</lastBuildDate>
        <generator>Jekyll v3.6.3</generator>
        
            <item>
                <title>The Single Source of Truth - Really?</title>
                <description>&lt;p&gt;In software engineering, there’s often talk of the Source of Truth. Which database is source of truth? Yours or mine? When things get complicated, does it still make sense to think about databases or single systems as the source of truth?&lt;/p&gt;

&lt;!--more--&gt;

&lt;p&gt;These conversations have all made the assumption that they can decide who has authority over reality. Unfortunately a database is never a source of truth. The word “source” means the point of origin. When thinking about the source of truth we need to think about where the data originates. Let’s look at a few examples.&lt;/p&gt;

&lt;h2&gt;An eCommerce Website&lt;/h2&gt;

&lt;p&gt;The website has an Orders database in the website, and you have a call centre platform with it’s own database. It seems like the source of truth would be  the website’s Orders database, and the call centre should accept that. But what happens when the call centre wants to take orders too. Does it have to send them through the website so the website can remain the source of truth? That’s obviously coupling and creating the website as a single point of failure.&lt;/p&gt;

&lt;h2&gt;Stock Management&lt;/h2&gt;

&lt;p&gt;Your business starts with 1 warehouse. You get stock levels from the warehouse database and import them in to your purchasing database (for admin staff) and sales database (for your customers). Clearly that warehouse is the source of truth.&lt;/p&gt;

&lt;p&gt;Now you expand and add another warehouse. The first warehouse cannot be the source of truth any more because it does not know about stock in the new warehouse, so you create a new stock levels database to hold a view of stock across multiple locations. Is THIS database the source of truth? If it becomes out of sync with the warehouse, it can’t be said to be truthful anymore. Saying any of these systems is the source of truth is clearly lieing to yourself.&lt;/p&gt;

&lt;h2&gt;The Reality&lt;/h2&gt;

&lt;p&gt;The reality? None of these systems is the source of truth. To really find the source of truth we have to expand our idea of a ‘system’ to include 3rd parties, software and importantly people, who make decisions and measure reality.&lt;/p&gt;

&lt;p&gt;For your eCommerce business, the customer is the source of truth for orders. They make the decision. Your website, mobile app or call centre is just the channel by which they place their order.&lt;/p&gt;

&lt;p&gt;For the stock management system, the source of truth is the staff in the warehouse, actually counting stock and recording it on their tablets. Both warehouse databases are equally the source of their respective truths.&lt;/p&gt;

&lt;p&gt;So the phrase Source of Truth doesn’t refer to which database I have built, it refers to the decision maker and the path by which data about that decision arrives with me.&lt;/p&gt;

&lt;h2&gt;Thinking Differently&lt;/h2&gt;

&lt;p&gt;If we stop to think about databases as an authority we can open ourselves up to other opportunities. Instead of deciding where data should live, we can start thinking about how it should get there. This means we can think in terms of integration and extension.&lt;/p&gt;

&lt;p&gt;Now, a more useful toolset to use is that of event streams. The events may come from multiple places or go to multiple places, but the stream is where you can find that truth.&lt;/p&gt;

&lt;p&gt;Databases become storage utilities, and the event stream becomes the centre of your system. Databases can be populated by reading the stream; notifications and other side effects can be triggered by things happening on the stream.&lt;/p&gt;

&lt;h2&gt;What does that mean for our examples?&lt;/h2&gt;

&lt;p&gt;Well, the eCommerce website and the call centre are both sources of orders. Your fulfilment centres, your business intelligence, even your accounts are all reacting to these orders when they happen.&lt;/p&gt;

&lt;p&gt;In your stock management system, you might still have a database that shows a view of stock in both warehouses, but it is nothing special. It is merely a projection of the stock keeping events which happened in your warehouses. And any side effects, sending notifications to your purchasing systems or updating business intelligence are triggered not by reading a single central database, bit by the stream.&lt;/p&gt;

&lt;h2&gt;So what, I hear you say&lt;/h2&gt;

&lt;p&gt;Changing the way we think is challenging, and there is always more to it than can be written in a single blog post.&lt;/p&gt;

&lt;p&gt;But thinking about the events that happened in our businesses, and how data about then flows allows us to build software that model our businesses, not make our business adhere to the software.&lt;/p&gt;

&lt;p&gt;Things are more extendable, more decoupled and easier to explain to non-technical colleagues.&lt;/p&gt;

&lt;p&gt;So next time you are thinking “where is the source of truth?”, try asking yourself “How does the truth get here?”, “Who cares about it?” and importantly, what event has occurred in your business that we care about?”&lt;/p&gt;

&lt;p&gt;You might just find a simpler way to build your software that better represents the reality you are working with.&lt;/p&gt;
</description>
                <pubDate>Mon, 26 Aug 2019 00:00:00 +0000</pubDate>
                <link>https://www.hidefsoftware.co.uk/2019/08/26/the-single-source-of-truth-really/</link>
                <guid isPermaLink="true">https://www.hidefsoftware.co.uk/2019/08/26/the-single-source-of-truth-really/</guid>
                
                <category>programming</category>
                
                <category>distributed systems</category>
                
                
            </item>
        
            <item>
                <title>Be A Grafana/Graphite Power User</title>
                <description>&lt;p&gt;Graphite (the time series stats database) and Grafana (the brilliant visualisation tool) both have excellent documentation that tells us how to operate the software, but simply knowing how to use a tool does not mean you truly grok it.&lt;/p&gt;

&lt;p&gt;This is a collection of tips and suggestions to help your use of these tools be easier and more meaningful.&lt;/p&gt;

&lt;!--more--&gt;

&lt;h1&gt;Grafana&lt;/h1&gt;

&lt;h2&gt;1. Summarise all graphs by a templated interval to allow easy control of data granularity&lt;/h2&gt;

&lt;p&gt;Grafana is pretty good at trying to guess the interval at which you want to summarise your data, but it doesn’t always get it right.&lt;/p&gt;

&lt;p&gt;Creating a &lt;a href=&quot;http://docs.grafana.org/reference/templating/&quot;&gt;template variable&lt;/a&gt; to control the interval of a &lt;a href=&quot;http://graphite.readthedocs.io/en/latest/functions.html#graphite.render.functions.summarize&quot;&gt;summarize(…)&lt;/a&gt; function on all graphs on your dashboard will let you control the granularity of an entire dashboard with ease.&lt;/p&gt;

&lt;p&gt;The default interval (1 minute)&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/posts/2018-06-28-be-a-grafana-power-user/interval-1m.png&quot; alt=&quot;1 minute interval&quot; /&gt;&lt;/p&gt;

&lt;p&gt;An overridden interval of 1 hour&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/posts/2018-06-28-be-a-grafana-power-user/interval-1h.png&quot; alt=&quot;1 hour interval&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Trends in the data can be made more obvious by viewing it at a different level of detail.&lt;/p&gt;

&lt;h2&gt;2. Use Points Draw Mode to make sparse data more visible&lt;/h2&gt;

&lt;p&gt;On sparse data, data points can appear as lines of zero length and be completely invisible. Enable the &lt;a href=&quot;http://docs.grafana.org/features/panels/graph/#draw-modes&quot;&gt;Points draw mode&lt;/a&gt; to make isolated stats stand out.&lt;/p&gt;

&lt;p&gt;I recommend a width of 1 to ensure that the points merely point the data out and do not blend together, hiding the true data.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/posts/2018-06-28-be-a-grafana-power-user/sparedatawithpoints.png&quot; alt=&quot;Sparse data rendered with Point Draw Mode&quot; /&gt;&lt;/p&gt;

&lt;h2&gt;3. Repeat panels and graphs for similar data&lt;/h2&gt;

&lt;p&gt;Standardise your stats, then use repeated Panels for repeated stat types. e.g. for each dependency, show timings, counts and errors.&lt;/p&gt;

&lt;p&gt;You can use template variables to create a list of options (even discover the available stats from your graphs), then configure a graph or panel to repeat for each item in the list.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/posts/2018-06-28-be-a-grafana-power-user/repeatpanels.png&quot; alt=&quot;Repeat a panel for each value in a templated variable&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Maintaining duplicate graphs will just give you a headache.&lt;/p&gt;

&lt;h2&gt;4. Share Snapshots&lt;/h2&gt;

&lt;p&gt;Dashboards and their data are changing all the time. If you share a link to a dashboard, the graphs may be changed or the data may be archived or reaggregated  in future, meaning that your link may no longer show the information you wanted to share.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/posts/2018-06-28-be-a-grafana-power-user/sharesnapshots.png&quot; alt=&quot;Share snapshot dialog&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Share a snapshot instead. Grafana will store your data and protect it from change so that it can be referenced from documentation or incident management with confidence.&lt;/p&gt;

&lt;h2&gt;5. Use non-linear scale for timings to make highly variable data understandable&lt;/h2&gt;

&lt;p&gt;Linear scale on the Y axis can often hide data. Outliers can cause the fine detail of the data to be invisible.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/posts/2018-06-28-be-a-grafana-power-user/yaxisscale-linear.png&quot; alt=&quot;Linear scale is only clear for outliers&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Using Log2 as the scale for the same data shows that the different data series are not exactly the same, and that smaller, but still significant spikes are more common.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/posts/2018-06-28-be-a-grafana-power-user/yaxisscale-log2.png&quot; alt=&quot;Log2 scale highlights smallers but significant change as well&quot; /&gt;&lt;/p&gt;

&lt;h1&gt;Graphite&lt;/h1&gt;

&lt;h2&gt;1. Never transform null timings to zero&lt;/h2&gt;

&lt;p&gt;In a sparse data set, it’s tempting to guarantee that your data does not have huge gaps in it. When counting a stat, e.g. requests per second, a gap in the data means that you got no requests. In this case applying &lt;a href=&quot;http://graphite.readthedocs.io/en/latest/functions.html#graphite.render.functions.transformNull&quot;&gt;transformNull(0)&lt;/a&gt; makes sense, filling in the gaps in the visualisation.&lt;/p&gt;

&lt;p&gt;Applying this function to timings, however is positively misleading. It would give the impression that, for periods of time, the timings were 0.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/posts/2018-06-28-be-a-grafana-power-user/transformnull-enabled.png&quot; alt=&quot;Timings with missing data transformed to 0&quot; /&gt;&lt;/p&gt;

&lt;p&gt;It now appears that our timings are very spiky and unreliable, which might not be the case.&lt;/p&gt;

&lt;p&gt;A gap in latency stats does NOT mean that the latency was 0. It means that no data was available. The gaps in the visualisation tell us that we are not able to answer any questions during that period.&lt;/p&gt;

&lt;p&gt;Omitting &lt;a href=&quot;http://graphite.readthedocs.io/en/latest/functions.html#graphite.render.functions.transformNull&quot;&gt;transformNull(0)&lt;/a&gt; from timing graphs highlights the present and absence of data much more clearly.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/posts/2018-06-28-be-a-grafana-power-user/transformnull-disabled.png&quot; alt=&quot;Timings with missing left blank&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Lack of data should not be hidden. Think carefully before transforming data to ensure that the transformation does not obscure the meaning of the data. Graphs are about making information visible, not hiding it.&lt;/p&gt;

&lt;h2&gt;2. Applying transformNull(0) before aggregation changes the meaning of the aggregation&lt;/h2&gt;

&lt;p&gt;If you &lt;em&gt;do&lt;/em&gt; choose to use &lt;a href=&quot;http://graphite.readthedocs.io/en/latest/functions.html#graphite.render.functions.transformNull&quot;&gt;transformNull(0)&lt;/a&gt;, do so before &lt;a href=&quot;http://graphite.readthedocs.io/en/latest/functions.html#graphite.render.functions.movingAverage&quot;&gt;movingAvg()&lt;/a&gt;, &lt;a href=&quot;http://graphite.readthedocs.io/en/latest/functions.html#graphite.render.functions.movingMedian&quot;&gt;movingMedian()&lt;/a&gt;, or other aggregate functions, otherwise the aggregations will not include the transformed data.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/posts/2018-06-28-be-a-grafana-power-user/transformfirst-thenaggregate.png&quot; alt=&quot;The difference between transforming first, or aggregating first&quot; /&gt;&lt;/p&gt;

&lt;h2&gt;3. Always specify your units&lt;/h2&gt;

&lt;p&gt;Always specify the unit of time, e.g. summarise(), movingAverage(), the default is the number of data points included.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/posts/2018-06-28-be-a-grafana-power-user/specifyunits.png&quot; alt=&quot;Failing to specify units on aggregations can lead to unpredictable results.&quot; /&gt;&lt;/p&gt;

&lt;p&gt;On sparse data, the last N data points may be 100 seconds or 100 minutes, you won’t know unless you double check the graphs generated against the source data.&lt;/p&gt;

&lt;hr /&gt;

&lt;p&gt;Useful links:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;http://docs.grafana.org/&quot;&gt;Grafana Documentation&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;http://graphite.readthedocs.io/en/latest/functions.html&quot;&gt;Graphite Documentation&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</description>
                <pubDate>Thu, 28 Jun 2018 00:00:00 +0000</pubDate>
                <link>https://www.hidefsoftware.co.uk/2018/06/28/be-a-grafana-power-user/</link>
                <guid isPermaLink="true">https://www.hidefsoftware.co.uk/2018/06/28/be-a-grafana-power-user/</guid>
                
                <category>grafana</category>
                
                <category>graphite</category>
                
                <category>monitoring</category>
                
                
            </item>
        
            <item>
                <title>Kubernetes February Round Up</title>
                <description>&lt;p&gt;Another month is passed, and it seems like activity around Kubernetes is accelerating exponentially.&lt;/p&gt;

&lt;!--more--&gt;

&lt;h3&gt;The New Stack&lt;/h3&gt;

&lt;p&gt;&lt;a href=&quot;https://thenewstack.io/ebooks/&quot;&gt;https://thenewstack.io/ebooks/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The New Stack has a collection of ebooks about adopting Kubernetes in an enterprise environment, covering topics including Monitoring, Security, Orchestration and several others.&lt;/p&gt;

&lt;h3&gt;What makes a cluster a cluster&lt;/h3&gt;

&lt;p&gt;&lt;a href=&quot;https://coreos.com/blog/cluster-osi-model.html&quot;&gt;https://coreos.com/blog/cluster-osi-model.html&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We deal with so many levels of abstraction that it can be hard to see how things should fit together. This interesting comparison with the OSI model can help get things in line.&lt;/p&gt;

&lt;h2&gt;Developing with Kubernetes&lt;/h2&gt;

&lt;h3&gt;Debugging Crash Loop Backoffs&lt;/h3&gt;

&lt;p&gt;&lt;a href=&quot;https://medium.com/kokster/debugging-crashloopbackoffs-with-init-containers-26f79e9fb5bf&quot;&gt;https://medium.com/kokster/debugging-crashloopbackoffs-with-init-containers-26f79e9fb5bf&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It can be infuriating when your container doesn’t start, and doesn’t even produce any logs. Here are some tips to help you figure out what’s going on.&lt;/p&gt;

&lt;h3&gt;Speed up development by mapping volumes&lt;/h3&gt;

&lt;p&gt;&lt;a href=&quot;https://github.com/vapor-ware/ksync&quot;&gt;https://github.com/vapor-ware/ksync&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Sometimes the whole Code, Build, Test, Code cycle can be too onerous, especially when you’re trying to get something working by trial an error. ksync helps you sync your local file system with a remote container to make live-changes.&lt;/p&gt;

&lt;h2&gt;Operating Kubernetes&lt;/h2&gt;

&lt;h3&gt;GKE vs AKS vs EKS&lt;/h3&gt;

&lt;p&gt;&lt;a href=&quot;https://blog.hasura.io/gke-vs-aks-vs-eks-411f080640dc&quot;&gt;https://blog.hasura.io/gke-vs-aks-vs-eks-411f080640dc&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Managed Kubernetes from 3 of the biggest cloud providers. Information on EKS is sadly lacking.&lt;/p&gt;

&lt;h3&gt;Paul Maddox - Amazon EKS&lt;/h3&gt;

&lt;p&gt;&lt;a href=&quot;https://blog.uprightvinyl.co.uk/2018/02/08/aws-builders-day-part-3-kubernetes-on-aws-with-amazon-eks/&quot;&gt;https://blog.uprightvinyl.co.uk/2018/02/08/aws-builders-day-part-3-kubernetes-on-aws-with-amazon-eks/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Addressing the lack of information about Amazon’s Elastic Kubernetes System, this write up of a talk by Paul Maddox probably covers 90% of what I have seen about EKS.&lt;/p&gt;

&lt;h3&gt;Kured&lt;/h3&gt;

&lt;p&gt;&lt;a href=&quot;https://github.com/weaveworks/kured&quot;&gt;https://github.com/weaveworks/kured&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Manages node reboots safely, to facilitate things like host OS updates&lt;/p&gt;

&lt;h3&gt;Providing IAM Roles to Containers&lt;/h3&gt;

&lt;p&gt;&lt;a href=&quot;https://github.com/jtblin/kube2iam&quot;&gt;https://github.com/jtblin/kube2iam&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;When coming from AWS services like EC2, where you choose the permissions you want and the relevant credentials are given to you, kube2iam brings this same pattern down in to your Kubernetes cluster.&lt;/p&gt;

&lt;h3&gt;KubeAPI AWS Authentication&lt;/h3&gt;

&lt;p&gt;&lt;a href=&quot;https://github.com/heptio/authenticator&quot;&gt;https://github.com/heptio/authenticator&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Initially Kubernetes only grants admin access to a single set of SSH keys. If you want to share API access with your team, and want to use AWS IAM users to manage it, this can help you out.&lt;/p&gt;
</description>
                <pubDate>Sat, 03 Mar 2018 00:00:00 +0000</pubDate>
                <link>https://www.hidefsoftware.co.uk/2018/03/03/kubernetes-february-round-up/</link>
                <guid isPermaLink="true">https://www.hidefsoftware.co.uk/2018/03/03/kubernetes-february-round-up/</guid>
                
                <category>kubernetes</category>
                
                
            </item>
        
            <item>
                <title>Kubernetes January Round Up</title>
                <description>&lt;p&gt;The first month of the year is gone and I have seen some interesting things come across my plate this year. So, I’ve pulled them together in case anyone else is interested.&lt;/p&gt;

&lt;!--more--&gt;

&lt;h2&gt;Operating Kubernetes&lt;/h2&gt;

&lt;h3&gt;Provisioning&lt;/h3&gt;

&lt;h4&gt;kops&lt;/h4&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/kubernetes/kops&quot;&gt;https://github.com/kubernetes/kops&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Part of the Kubernetes project itself, kops targets AWS and GCE, allowing you to spin up a production grade cluster in minutes. It is well documented and well supported, which makes it one of the best tools I have seen. Unfortunately it stores configuration and keys in S3 and requires access to them at runtime, which would make it hard to create push-button deployments for test clusters. That said, if you want to run your own cluster in AWS, this should probably be your first port of call.&lt;/p&gt;

&lt;h4&gt;Kubicorn&lt;/h4&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/kris-nova/kubicorn&quot;&gt;https://github.com/kris-nova/kubicorn&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Although not an official part of the Kubernetes project, Kubicorn aims to make it easier to provision clusters on more providers than just AWS and GCE, while giving your more flexibility. If you want to spin up a test cluster on A.N.Other cloud provider, this is probably what you want. Be aware though, it is not yet rated production ready, as it is lacking some key aspects, including depth of documentation.&lt;/p&gt;

&lt;h3&gt;Friendly DNS&lt;/h3&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/kubernetes-incubator/external-dns&quot;&gt;https://github.com/kubernetes-incubator/external-dns&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Kubernetes provides it’s own DNS service, but this is intended entirely for service discovery, with meaningful but ugly DNS names like &lt;code class=&quot;highlighter-rouge&quot;&gt;my-svc.my-namespace.svc.cluster.local&lt;/code&gt; or &lt;code class=&quot;highlighter-rouge&quot;&gt;pod-ip-address.my-namespace.pod.cluster.local&lt;/code&gt;. If you want to expose a site or API to users, use external-dns to provision vanity URLs for services or ingress. It supports a number of providers including Route53, AzureDNS, CloudFlare and, once the controller has been deployed, you only need to decorate your services to make us of it.&lt;/p&gt;

&lt;h3&gt;Custom DNS&lt;/h3&gt;
&lt;p&gt;&lt;a href=&quot;https://kubernetes.io/docs/tasks/administer-cluster/dns-custom-nameservers/&quot;&gt;https://kubernetes.io/docs/tasks/administer-cluster/dns-custom-nameservers/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;As mentioned, Kubernetes’ DNS service is used for internal service discovery. If you have your own private DNS that you want to integrate with, you can easily extend Kubernetes DNS with your own DNS as a fallback.&lt;/p&gt;

&lt;h2&gt;Developing For Kubernetes&lt;/h2&gt;

&lt;h3&gt;Service Meshes&lt;/h3&gt;

&lt;h4&gt;Linkerd&lt;/h4&gt;
&lt;p&gt;&lt;a href=&quot;https://linkerd.io/&quot;&gt;https://linkerd.io/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Tried and tested by the likes of Monzo, Linkerd can transparently handle retries, time outs, rate-limiting, circuit breaking and the like, removing the need for tangled resilience implementations with Polly.net or custom code. It even has multiple service discovery plugins, for example using consul to resolve service hosts directly, removing the need for internal load balancers.&lt;/p&gt;

&lt;h4&gt;Istio&lt;/h4&gt;
&lt;p&gt;&lt;a href=&quot;https://istio.io/&quot;&gt;https://istio.io/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;More than a single service, Istio is an entire service mesh project. Combining resilience, service discovery, load balancing etc. with a TLS secured mesh, capable of being extended outside of your cluster. It’s not as mature as Linkerd yet, but it has lofty goals and broad community support. One to watch.&lt;/p&gt;

&lt;h3&gt;AWS ELB Config Options&lt;/h3&gt;
&lt;p&gt;&lt;a href=&quot;https://gist.github.com/mgoodness/1a2926f3b02d8e8149c224d25cc57dc1&quot;&gt;https://gist.github.com/mgoodness/1a2926f3b02d8e8149c224d25cc57dc1&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We live in an AWS world. Most AWS configurations provision ELBs to expose services outside the cluster. Here is a handy cheat sheet for configuring your load balancer, e.g. internal only, SSL certs, time outs.&lt;/p&gt;

&lt;h3&gt;Docker for Mac /w Kubernetes&lt;/h3&gt;
&lt;p&gt;&lt;a href=&quot;https://blog.alexellis.io/docker-for-mac-with-kubernetes/&quot;&gt;https://blog.alexellis.io/docker-for-mac-with-kubernetes/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now Docker for Mac has Kubernetes support built in Alex Ellis, author of OpenFaas, compares it to some of the other options for Kubernetes developer tooling, and a quick view of running OpenFaas (a kubernetes based, vendor agnostic, alternative to Lambda)&lt;/p&gt;

&lt;h3&gt;Docker for Windows /w Kubernetes&lt;/h3&gt;
&lt;p&gt;&lt;a href=&quot;https://goo.gl/LUTFjS&quot;&gt;https://goo.gl/LUTFjS&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Thanks to Mr. Hanselman for running through the new release of Docker for Windows with one-click Kubernetes built in, as well as a flying example of the Kubernetes Dashboard, and deploying ASP.NET Core in to Kubernetes.&lt;/p&gt;

&lt;hr /&gt;

&lt;p&gt;If you want to contribute anything to this list, or my next round up, drop me an email at &lt;a href=&quot;mailto:roundup@hidefsoftware.co.uk&quot;&gt;roundup@hidefsoftware.co.uk&lt;/a&gt;.&lt;/p&gt;
</description>
                <pubDate>Fri, 02 Feb 2018 00:00:00 +0000</pubDate>
                <link>https://www.hidefsoftware.co.uk/2018/02/02/kubernetes-january-round-up/</link>
                <guid isPermaLink="true">https://www.hidefsoftware.co.uk/2018/02/02/kubernetes-january-round-up/</guid>
                
                <category>kubernetes</category>
                
                
            </item>
        
            <item>
                <title>Getting Started With .NET Core on Mac or Linux</title>
                <description>&lt;p&gt;I’ve been writing C# for years and that has meant that I have always been constrained to working with Windows.&lt;/p&gt;

&lt;p&gt;Not any more! With the advent of .NET Core and Visual Studio Code I am finally free to move completely to a Unix environment and take advantage of all the tools and technologies that are available in Mac OS or Linux environments.&lt;/p&gt;

&lt;p&gt;Here are the steps I took to get my new .NET Core projects up and running.&lt;/p&gt;

&lt;!--more--&gt;

&lt;p&gt;(I usually work on a Mac, but for the purposes of this article, everything I write here has been tested on a clean Debian installation.)&lt;/p&gt;

&lt;h2&gt;Install .NET Core&lt;/h2&gt;

&lt;p&gt;Getting .NET Core environment installed is actually quite simple. Unfortunately each platform has a slightly different approach so you’re best heading over to the  &lt;a href=&quot;https://www.microsoft.com/net/core&quot;&gt;.NET Core Download Page&lt;/a&gt; and following the instructions for your environment. At the time of writing, the latest version (and the version I will be referencing later on is 1.1.0).&lt;/p&gt;

&lt;h2&gt;The IDE&lt;/h2&gt;

&lt;p&gt;Visual Studio Code is a god send. Unlike it’s namesake Visual Studio, Code is fully cross platform (being implemented on top of Github’s Electron platform). It’s also very light weight.&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://code.visualstudio.com/download&quot;&gt;Download&lt;/a&gt; Visual Studio Code and install it. This will take minutes as the download package is only 51meg for mac.&lt;/p&gt;

&lt;p&gt;There are three points about Visual Studio Code that make it a winner for me:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;It’s light weight&lt;/li&gt;
  &lt;li&gt;It’s got support for many different languages/environments&lt;/li&gt;
  &lt;li&gt;It’s a fully fledged IDE with debugger and intellisense support.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Once installed, it’s helpful to install the &lt;code class=&quot;highlighter-rouge&quot;&gt;code&lt;/code&gt; command so you can launch Code and open files from the command line, e.g. &lt;code class=&quot;highlighter-rouge&quot;&gt;code myfile.cs&lt;/code&gt;.&lt;/p&gt;

&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;Cmd/Ctrl + P &amp;gt; Install 'code' command
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3&gt;Extensions&lt;/h3&gt;

&lt;p&gt;There are many extensions but we only need one right now. Hit &lt;code class=&quot;highlighter-rouge&quot;&gt;Cmd/Ctrl + Shift + X&lt;/code&gt; to open the Extensions manager and install the &lt;code class=&quot;highlighter-rouge&quot;&gt;C#&lt;/code&gt; extension. Once you’ve found the extension you want, it’s a click to install it, and then it’ll ask you to restart the window. You’ll be ready in seconds.&lt;/p&gt;

&lt;h2&gt;Creating A New Project&lt;/h2&gt;

&lt;h3&gt;Yeoman&lt;/h3&gt;

&lt;p&gt;I use Yeoman to do alot of the boiler plate when creating a new project. the &lt;code class=&quot;highlighter-rouge&quot;&gt;aspnet-generator&lt;/code&gt; is published by the &lt;a href=&quot;http://www.omnisharp.net/&quot;&gt;Omnisharp&lt;/a&gt; team which, although not endorsed by Microsoft, are closely connected to the Visual Studio Code team, and the C# extension (a core extension for Code) is driven by Omnisharp.&lt;/p&gt;

&lt;h4&gt;Install Node&lt;/h4&gt;

&lt;h5&gt;Mac with Home Brew&lt;/h5&gt;

&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;brew install node
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h5&gt;Ubuntu/Debian&lt;/h5&gt;

&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;curl -sL https://deb.nodesource.com/setup_7.x | sudo -E bash -
sudo apt-get install -y nodejs
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h4&gt;Install yeoman&lt;/h4&gt;

&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;npm i -g yo
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h4&gt;Install the omnisharp dotnet generator&lt;/h4&gt;

&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;npm i -g generator-aspnet
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h4&gt;Create the Library&lt;/h4&gt;

&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;yo aspnet
&amp;gt; Class Library
Name: CoreBootstrapLibrary
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h4&gt;Create the test Library&lt;/h4&gt;

&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;yo aspnet
&amp;gt; Unit test project (xUnit.net)
Name: CoreBootstrapLibrary.Tests
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h4&gt;Update Yeoman’s .NET version&lt;/h4&gt;

&lt;p&gt;Yeoman is quite helpful, but it &lt;em&gt;does&lt;/em&gt; make a few assumptions that don’t quite fit.&lt;/p&gt;

&lt;p&gt;Firstly, the Yeoman generated code references NetCore 1.0.1, but the currently latest version is 1.1.0, so we need to upgrade the test project. In &lt;code class=&quot;highlighter-rouge&quot;&gt;/CoreBootstrapLibrary.Tests/project.json&lt;/code&gt; we must change &lt;code class=&quot;highlighter-rouge&quot;&gt;Microsoft.NetCore.App&lt;/code&gt; version from &lt;code class=&quot;highlighter-rouge&quot;&gt;1.0.1&lt;/code&gt; to &lt;code class=&quot;highlighter-rouge&quot;&gt;1.1.0&lt;/code&gt; so file contains this section:&lt;/p&gt;

&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&quot;frameworks&quot;: {
    &quot;netcoreapp1.0&quot;: {
        &quot;dependencies&quot;: {
            &quot;Microsoft.NETCore.App&quot;: {
                &quot;type&quot;: &quot;platform&quot;,
                &quot;version&quot;: &quot;1.1.0&quot;
            }
        }
    }
},
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h4&gt;Tidy up Yeoman’s .gitignore files&lt;/h4&gt;

&lt;p&gt;Secondly let’s remove the .gitignore files from the project folders and put them at the root of the project.&lt;/p&gt;

&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;mv CoreBootstrapLibrary/.gitignore ./.gitignore
rm CoreBootstrapLibrary.Tests/.gitignore
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h4&gt;Initiate our directory as a git repo&lt;/h4&gt;

&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;git init
git add .
git commit -m &quot;Initial Commit&quot;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h4&gt;Restore initial dependencies&lt;/h4&gt;

&lt;p&gt;Finally we need to retrieve all the referenced dependencies, again through the CLI. This will restore all dependencies on all projects in child directories. The first time you run this, it has to download the whole of .NET which, althought not as big as in the old days, still takes a few minutes.&lt;/p&gt;

&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;dotnet restore
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2&gt;Add Some Code and Tests&lt;/h2&gt;

&lt;p&gt;I’m not going to walk you through writing code or tests, but obviously we need to make the test project reference the code project.&lt;/p&gt;

&lt;p&gt;In &lt;code class=&quot;highlighter-rouge&quot;&gt;CoreBootstrapLibrary.Tests/project.json&lt;/code&gt;:&lt;/p&gt;

&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;{
    ...
    &quot;dependencies&quot;: {
        ...
        &quot;CoreBootstrapLibrary&quot;: {
            &quot;target&quot;: &quot;project&quot;
        }
    },
    ...
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;We can run the tests using the &lt;code class=&quot;highlighter-rouge&quot;&gt;test&lt;/code&gt; command:&lt;/p&gt;

&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;cd tests
dotnet test
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2&gt;Publishing Nuget Packages with AppVeyor&lt;/h2&gt;

&lt;p&gt;There are many cloud CI services out there, and finding one which is right for you is like buying a new pair of shoes. However I don’t think I need to explain the meaning of Agile, so I’m going to recommend that you dive in with &lt;a href=&quot;https://www.appveyor.com/&quot;&gt;AppVeyor&lt;/a&gt; and then you can change your mind later, if you see fit.&lt;/p&gt;

&lt;p&gt;AppVeyor has a nice interface for adding new projects, is configurable using an &lt;code class=&quot;highlighter-rouge&quot;&gt;appveyor.yml&lt;/code&gt; file in the root of your project, supports .NET Core natively, and it’s all round pretty good. It even has a free tier for public projects and is quite reasonably priced for private ones too, and mighty fine documentation to boot.&lt;/p&gt;

&lt;p&gt;Follow the &lt;a href=&quot;https://www.appveyor.com/docs/&quot;&gt;Documentation&lt;/a&gt; for how to set up a new project in AppVeyor, and you’ll have it hooked up to your git repo in no time. Then we just need to get it configured.&lt;/p&gt;

&lt;p&gt;By default AppVeyor will discover traditional .Net solutions, build them and run tests. They’re not that slick for .NET Core yet though.&lt;/p&gt;

&lt;p&gt;Dropping this script in the root of your repository will tell AppVeyor how to build, test and publish our project as a NuGet package.&lt;/p&gt;

&lt;p&gt;&lt;code class=&quot;highlighter-rouge&quot;&gt;appveyor.yml&lt;/code&gt;&lt;/p&gt;

&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;version: 0.0.{build}
build_script:
- cmd: |
    dotnet restore
    dotnet build CoreBootstrapLibrary
    dotnet pack CoreBootstrapLibrary
test_script:
- cmd: &amp;gt;
    dotnet test CoreBootstrapLibrary.Tests
artifacts:
- path: '**/*.nupkg'
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The last bit of AppVeyor goodness is that any &lt;code class=&quot;highlighter-rouge&quot;&gt;*.nupkg&lt;/code&gt; files published as artifacts (in this case, our library and it’s symbols) are published to a private NuGet feed for your account. Access control is a bit of a blunt instrument, but for internal teams, or your own private projects, it’s a great starting point.&lt;/p&gt;

&lt;h2&gt;Consuming Private Nuget Packages&lt;/h2&gt;

&lt;p&gt;One of the differences of working in a Unix environment, rather than Windows, is that we no longer configure Visual Studio with our NuGet sources. Instead we configure them in our environment. Our per-user NuGet.config file is located at `    ~/.nuget/NuGet/NuGet.config`, so to add our private NuGet source with our newly built packages, we just have to add a PackageSource and a set of credentials.&lt;/p&gt;

&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;cp&quot;&gt;&amp;lt;?xml version=&quot;1.0&quot; encoding=&quot;utf-8&quot;?&amp;gt;&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;&amp;lt;configuration&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;packageSources&amp;gt;&lt;/span&gt;
        ...
        &lt;span class=&quot;nt&quot;&gt;&amp;lt;add&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;key=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Appveyor&quot;&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;value=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;https://ci.appveyor.com/nuget/&amp;lt;my account&amp;gt;&quot;&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;/packageSources&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;packageSourceCredentials&amp;gt;&lt;/span&gt;
        &lt;span class=&quot;nt&quot;&gt;&amp;lt;Appveyor&amp;gt;&lt;/span&gt;
            &lt;span class=&quot;nt&quot;&gt;&amp;lt;add&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;key=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Username&quot;&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;value=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;&amp;lt;appveyor email&amp;gt;&quot;&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;/&amp;gt;&lt;/span&gt;
            &lt;span class=&quot;nt&quot;&gt;&amp;lt;add&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;key=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;ClearTextPassword&quot;&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;value=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;&amp;lt;appveyor password&amp;gt;&quot;&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;/&amp;gt;&lt;/span&gt;
        &lt;span class=&quot;nt&quot;&gt;&amp;lt;/Appveyor&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;/packageSourceCredentials&amp;gt;&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;&amp;lt;/configuration&amp;gt;&lt;/span&gt;  
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Unfortunately I wasn’t able to find any documentation on how to encrypt the password stored in this file, other than that it is possible to do so.&lt;/p&gt;

&lt;p&gt;Now we can create an application to consume this package.&lt;/p&gt;

&lt;h2&gt;Creating a Service&lt;/h2&gt;

&lt;p&gt;One of the beauties of a Unix environment is that you don’t need to create complicated Windows Services with an installer and a framework like TopShelf (much respect to TopShelf, but it’s filling a gap that should never have existed).&lt;/p&gt;

&lt;p&gt;A service can be a simple console application. You can run it on the console yourself to test it, or you can run it using &lt;code class=&quot;highlighter-rouge&quot;&gt;upstart&lt;/code&gt; in Ubuntu or inside a docker container in exactly the same way.&lt;/p&gt;

&lt;p&gt;So in a new directory for a new project:&lt;/p&gt;

&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;yo aspnet
&amp;gt; Console application
CoreBootstrapApp
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The new console application can by run using the &lt;code class=&quot;highlighter-rouge&quot;&gt;run&lt;/code&gt; command:&lt;/p&gt;

&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;cd CoreBootstrapApp
dotnet run
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Next we add the dependency for the NuGet package we created in &lt;code class=&quot;highlighter-rouge&quot;&gt;CoreBootstrapApp/project.json&lt;/code&gt;:&lt;/p&gt;

&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;{
    ...
    &quot;dependencies&quot;: {
        ...
        &quot;CoreBootstrapLibrary&quot;: &quot;*&quot;,
    },
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;And the rest is up to you. You can now write library code, publish it to a private NuGet feed using AppVeyor and consume it from your own application.&lt;/p&gt;

&lt;p&gt;Another time I will write about hosting your new .NET Core application in Docker courtesy of Heroku, and implementing a 12 Factor app using .NET Core.&lt;/p&gt;

&lt;p&gt;You can find the code for these example projects here:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;https://github.com/hidef/CoreBootstrapLibrary&quot;&gt;CoreBootstrapLibrary&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://github.com/hidef/CoreBootstrapApp&quot;&gt;CoreBootstrapApp&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And CI for the Library over at &lt;a href=&quot;https://ci.appveyor.com/project/uatec/corebootstraplibrary&quot;&gt;AppVeyor&lt;/a&gt;&lt;/p&gt;

</description>
                <pubDate>Tue, 03 Jan 2017 00:00:00 +0000</pubDate>
                <link>https://www.hidefsoftware.co.uk/2017/01/03/getting-started-with-core-on-mac-or-linux/</link>
                <guid isPermaLink="true">https://www.hidefsoftware.co.uk/2017/01/03/getting-started-with-core-on-mac-or-linux/</guid>
                
                <category>.NET Core</category>
                
                <category>Mac</category>
                
                <category>Linux</category>
                
                <category>Visual Studio Code</category>
                
                
            </item>
        
            <item>
                <title>The Caching Antipattern</title>
                <description>&lt;p&gt;TL;DR - Caching done badly has bad implications. Try your hardest not to cache data; but if you really do have to, make sure you do it right.&lt;/p&gt;

&lt;hr /&gt;

&lt;blockquote&gt;
  &lt;p&gt;There are only two hard things in Computer Science: cache invalidation and naming things.&lt;/p&gt;

  &lt;p&gt;– Phil Karlton&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;Caching?&lt;/h2&gt;

&lt;p&gt;To make sure we’re on the same page, when I say &lt;em&gt;caching&lt;/em&gt;, I am talking about the practice of speeding up your own application by masking slow dependencies by remembering previous responses and using them instead of making another slow call to the dependency.&lt;/p&gt;

&lt;p&gt;As mentioned briefly by Phil Karlton in his well known sound byte, caching is a tricky problem. This is compounded by a number of common mistakes which I have seen over the years which have resulted in unnecessary confusion delay.&lt;/p&gt;

&lt;h2&gt;Common Mistakes&lt;/h2&gt;

&lt;p&gt;Here are some of the mistakes I have seen and what we should to differently.&lt;/p&gt;

&lt;!--more--&gt;

&lt;h3&gt;Cache on startup&lt;/h3&gt;

&lt;p&gt;When you know for a fact that your dependencies are so slow they are unusable so you don’t even try at run time. Pre-populating a cache as your application starts up rather than querying your dependent service is just admitting that your dependency is not fit for purpose. If it’s a 3rd party service then maybe there is nothing you can do about that, but all too often this technique is used to avoid having to make your services actually fit for purpose.&lt;/p&gt;

&lt;p&gt;The problem with caching like this is that it extends application startup times, making scaling and failure recovery hard or even non-viable.&lt;/p&gt;

&lt;p&gt;And even if you are okay with having a service that is slow to start or restart (which you shouldn’t be) it results in a cache which bares no relation to the nature of the data or your service’s usage patterns. A simple cache expiry policy would be meaningless here, as this pattern explicitly exists to avoid ever hitting your dependencies again.&lt;/p&gt;

&lt;h3&gt;Caching too early&lt;/h3&gt;

&lt;p&gt;I don’t mean too early in the request life cycle, I mean too early in the development cycle. Many times I have seen developers write code, decide that it is too slow, and stick cache in front of it.&lt;/p&gt;

&lt;p&gt;From that point on, the fact that their service is slow is hidden. There is no reason to optimise or improve the solution as the cache will ensure that the second request is much quicker; so why worry, right?&lt;/p&gt;

&lt;h3&gt;Integrated Cache&lt;/h3&gt;

&lt;p&gt;What’s the S in SOLID? Single Responsibility. If your caching capability is integrated directly into your service layer and you can’t run without it you’re definitely in breach of this principle. It’s not my place to extol the virtues of this principle here.&lt;/p&gt;

&lt;h3&gt;Caching everything&lt;/h3&gt;

&lt;p&gt;Blindly applying caching to every external call to ensure responsiveness without a thought for the implications. Even worse, this approach can result in developers and operators not even knowing that caching is occurring and making assumptions about the reliability of the underlying services that are simply not true.&lt;/p&gt;

&lt;h3&gt;Recaching&lt;/h3&gt;

&lt;p&gt;Caching everything, or even just caching a lot, can result in caches that cache caches which cache caches.&lt;/p&gt;

&lt;p&gt;On one end of the spectrum, this might result in all internal caches expiring shortly before the very front cache, resulting in a huge waste of time and resources to operate layers and layers of caching which are never used.&lt;/p&gt;

&lt;p&gt;At the other end, it can result in effectively summing the cache expiry time of all caches involved, so 10 layers of 30 minutes caches can result in a system serving data which is 5 hours old. How’s &lt;em&gt;that&lt;/em&gt; for counterintuitive?&lt;/p&gt;

&lt;h3&gt;Unflushable Cache&lt;/h3&gt;

&lt;p&gt;Occasionally cache implementations may back on to data stores like Redis which has management tooling which can be used to flush the cache on demand.&lt;/p&gt;

&lt;p&gt;Other implementations, such as hand cranks in memory caches or even caches provided by mainstream frameworks will not expose any cache management tools. This leaves ops with the only option, to restart the service to flush the memory. (Or worse, know enough about the cache implementation to find it’s location on file system and clear it out manually.)&lt;/p&gt;

&lt;p&gt;I have seen releases that have taken hours longer than necessary while different members of the team tried to track down cache after cache, flush them with a restart or wait for expiry before moving on to the next layer. All the time with a system taken offline because the system cannot be said to consist within itself.&lt;/p&gt;

&lt;h2&gt;The implications&lt;/h2&gt;

&lt;p&gt;Caching data has implications which these mistakes can accentuate as well as new issues we didn’t have to think of before.&lt;/p&gt;

&lt;p&gt;Deploying to a heavily cached system can be extremely time consuming while you wait for caches to expire or have to bust every cache you can find. Even in systems such as some CDNs I have worked with which serve traffic to tens of percent of the Internet and are thought to be the leaders in content delivery, flushing content and configuration caches globally can take up to two hours. This doesn’t need to be the case (&lt;a href=&quot;https://www.fastly.com/products/instant-purging&quot;&gt;Fastly&lt;/a&gt; can purge their cache in 150ms), and it results in confusion (is the new data being served yet?).&lt;/p&gt;

&lt;p&gt;The natural response is to come up with a workaround, usually some cache busting. Let’s just think about that. A work around for a feature that you just implemented. Think about the time, effort and cognitive load just to compensate for time, effort and cognitive load you just spent on caching in the first place.&lt;/p&gt;

&lt;p&gt;Debugging a cached system also becomes a challenge as being up to your neck in a tricky debugging session will end up with unrelated things being missed or forgotten about. 3 hours of confusion later you realise that you haven’t actually been testing any of the changes you have been making.&lt;/p&gt;

&lt;h2&gt;What we should do instead&lt;/h2&gt;

&lt;h3&gt;Don’t cache!&lt;/h3&gt;

&lt;p&gt;Okay, sometimes caching is your only option. You’re on the web and it’s happening whether you like it or not. But even in this case there are options other than simply slapping on &lt;code class=&quot;highlighter-rouge&quot;&gt;Cache-Control: max-age=xxx&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;Know your data&lt;/h3&gt;

&lt;p&gt;You should know when your data was last modified at the very least. Now you can make use of the &lt;code class=&quot;highlighter-rouge&quot;&gt;If-Modified-Since&lt;/code&gt; header. Return 304-not-modified if the data hasn’t changed. Now you can intelligently utilise the the client’s caching capability without sacrificing visibility and control. Using this header will let you serve new content instantly and also cache indefinitely. The best of both worlds. Taking it one step further, if you’re able to version your data (or just generate a hash of your response), you can make use of &lt;a href=&quot;https://en.wikipedia.org/wiki/HTTP_ETag&quot;&gt;etags&lt;/a&gt; and still interact with the client and apply appropriate logic without the latency related to data transfer.&lt;/p&gt;

&lt;h3&gt;Optimise for performance, don’t hide bad performance&lt;/h3&gt;

&lt;p&gt;Invest in profiler tooling. Find out why your application is slow and fix it. Reduce duplicate execution paths. Sort out bad query execution plans. Utilise indexes properly. If you’re using S3 or blob storage for your data you can always build your own index using Redis or the like. Redis is more than a cache. You can use it intelligently and get many benefits without the problems introduced by caching.&lt;/p&gt;

&lt;h2&gt;Finale&lt;/h2&gt;

&lt;p&gt;Caching is a useful tool, but can be easily abused without giving any signs of the abuse.&lt;/p&gt;

&lt;p&gt;Don’t get involved with caching till the last minute; find any other way you can first. Optimise your application before you use the blunt tool of caching.&lt;/p&gt;

&lt;p&gt;If you’ve come across any fundamental problems caused by caching and bad discipline there of, let me know and I can add them to the list.&lt;/p&gt;

</description>
                <pubDate>Sun, 25 Dec 2016 00:00:00 +0000</pubDate>
                <link>https://www.hidefsoftware.co.uk/2016/12/25/the-caching-antipattern/</link>
                <guid isPermaLink="true">https://www.hidefsoftware.co.uk/2016/12/25/the-caching-antipattern/</guid>
                
                <category>caching</category>
                
                <category>Software Development</category>
                
                
            </item>
        
            <item>
                <title>Can we do better than database migrations?</title>
                <description>&lt;p&gt;TL;DR - Database migrations are an antipattern, Model your database and generate diff scripts instead.&lt;/p&gt;

&lt;hr /&gt;

&lt;h2&gt;What are database migrations?&lt;/h2&gt;

&lt;p&gt;Migration is about getting from one place to another. Database migrations are about getting our database from one state to another. As we maintain and develop a database application we will want to add and remove new tables and columns, create new stored procedures and modify reference data.&lt;/p&gt;

&lt;p&gt;SQL database engines facilitate all of these operations with imperative statements so when playing with the database in your Dev environment we issue a series of these statements to get the database in to the state we want. When we’re happy that we know what we want, we write all of these statements down together (either directly in SQL or using a DSL like ruby or asp migrations) and declare them as a migration from the previous state to the new.&lt;/p&gt;

&lt;!--more--&gt;

&lt;h2&gt;Why do we use them?&lt;/h2&gt;

&lt;p&gt;The natural parity between making the changes ourselves and writing a migration to make them for us is obvious.&lt;/p&gt;

&lt;p&gt;It also fits neatly in to any change control process that demands a list of all changes prior to the change being deployed.&lt;/p&gt;

&lt;p&gt;Thirdly in an environment where databases are maintained by developers but owned by DBAs, it gives the DBAs a perfect opportunity to review the exact changes that are being applied.&lt;/p&gt;

&lt;h2&gt;Similarities&lt;/h2&gt;

&lt;p&gt;Managing your production environment by managing the transition between different states is reminiscent of version control, where a Version Control System keeps a record of the changes between versions, rather than of the individual states.&lt;/p&gt;

&lt;h2&gt;Alternatives&lt;/h2&gt;

&lt;p&gt;In the opening section I mentioned that SQL database engines provide an imperative language for modifying a database’s state (in contrast to the declarative language used to retrieve data). Declarative approaches are useful as they make the primary purpose of the code to communicate the desired behaviour or state, rather than how this should be achieved. Think HTML, CSS or even XML.&lt;/p&gt;

&lt;p&gt;The technique of database modelling (using tools such as Microsoft’s database projects, Redgate’s SQL Source Control or Toad Data Modeller) let us work in this way. By defining how you want your database to look rather than how it should be changed, your source code will always tell you what your database looks like at a glance. While these tools provide a UI and maybe even a visual language for describing your database, their real power is in implementing your database changes.&lt;/p&gt;

&lt;p&gt;If you have a version controlled model of your database it becomes a complex but conceptually neat task for generating the difference between any database at any version and any source code at any other version. This means that, using one of these tools will allow you to move a database easily from any one state, to any other state.&lt;/p&gt;

&lt;h2&gt;Disadvantages of Migrations&lt;/h2&gt;

&lt;p&gt;Migrations only allow you to move one step at a time and (due to the developer mindset of always looking forwards) can be irreversible if you are not vigilant.&lt;/p&gt;

&lt;p&gt;Working with a long established project results in a need to apply hundred, if not thousands, of migrations to construct a new database. This can be mitigated by taking a snapshot from your production, but this means you are replicating production back in to test and dev, rather than your source code being authoritative.&lt;/p&gt;

&lt;p&gt;Your source code does not describe your database, only how to construct your database. Drupal patches, for example are code which is distributed this way; however, I have seen this cause confusion as looking at the code and the patches can never give you a full picture. Applying your migrations is mandatory to get an idea of the true nature of your database.&lt;/p&gt;

&lt;p&gt;Because migrations must necessarily have an order, this order is usually defined by file name convention. This makes managing migrations in a very active project with Pull Requests a tiresome task. Every time a PR is merged ahead of you, you must rename your migration to ensure that it is applied after the newly merged migration. This is essentially duplicating the synchronisation work done inherently by your Version Control System.&lt;/p&gt;

&lt;h2&gt;In Conclusion&lt;/h2&gt;

&lt;p&gt;While migrations appear to offer a solution which matches our ways of working and sometimes our change process; they could be considered counterproductive when it comes to communicating intent in our code.&lt;/p&gt;

&lt;p&gt;There are a great many tools available to work with migrations but it seems as if these tools are merely automating a process which becomes too unwieldy when we try to do it at any scale. They are not changing the way we work for the better but making an antipattern continue to be viable for longer, while the adoption of these tools can make it harder to change our approach in future.&lt;/p&gt;

&lt;p&gt;While simple migration tools which work directly with SQL files or other raw data files offer us little, other migrations tools, e.g. Rails migrations, offer a layer of abstraction away from the dataset underneath. This can be used to provide portability and improve readability, so these tools can help us progress.&lt;/p&gt;

&lt;p&gt;Fundamentally though, it appears that we need to think about describing our desired state and using tools to help us get there, as we do in many other areas of our discipline.&lt;/p&gt;

&lt;p&gt;We should think long and hard before adopting migrations to manage a database, and we should put serious thought in to modelling as an alternative approach.&lt;/p&gt;
</description>
                <pubDate>Sat, 24 Dec 2016 00:00:00 +0000</pubDate>
                <link>https://www.hidefsoftware.co.uk/2016/12/24/can-we-do-better-than-database-migrations/</link>
                <guid isPermaLink="true">https://www.hidefsoftware.co.uk/2016/12/24/can-we-do-better-than-database-migrations/</guid>
                
                <category>databases</category>
                
                <category>SQL</category>
                
                <category>Software Development</category>
                
                
            </item>
        
            <item>
                <title>Management as Multithreading - Part 2</title>
                <description>&lt;p&gt;&lt;a href=&quot;/2016/11/02/management-as-multithreading&quot;&gt;Previously&lt;/a&gt; I wrote about how management of a team and multiple work flows can be viewed as similar to the multithreading problem in software development. I also explored some key multithreading concepts and compared them to work management.&lt;/p&gt;

&lt;p&gt;There are more concepts that are worth exploring.&lt;/p&gt;

&lt;!--more--&gt;

&lt;h3&gt;Thread Pooling&lt;/h3&gt;

&lt;p&gt;When you have a number of long running tasks in software it makes sense to spin up a thread for a task, and when the task is complete, tear that task down. If you are managing an organisation you might do the same; hire a contractor for 6 months to do a task and when the task is complete, let the contractor go.&lt;/p&gt;

&lt;p&gt;If you have many different tasks cropping up all the time, the effort of finding a contactor for each task, like the time involved in starting a new thread, can become prohibitively expensive. This is why organisations employ permanent staff which are kept on retainer for when they are needed, and why a thread pool can be used to have threads ready and waiting to perform tasks.&lt;/p&gt;

&lt;p&gt;By maintaining a pool of workers or threads, which will will perform tasks on demand we can remove the overhead of beginning and ending threads/employment if we are willing to take on the cost of maintaining these while there is no work available.&lt;/p&gt;

&lt;p&gt;In practice an organisation is always able to find work for us to do.&lt;/p&gt;

&lt;h3&gt;Task Scheduling&lt;/h3&gt;

&lt;p&gt;In computing the term ‘scheduling’ can mean more than one thing:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Choosing ‘when’ a task runs. Fairly obvious.&lt;/li&gt;
  &lt;li&gt;Choosing ‘where’ a task runs. If there are a number of environments with different capabilities, a memory set of tasks may be routinely scheduled to run in an environment with a lot of memory, or highly parallel work on an environment with a GPU.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;As humans we don’t have RAM or SSDs or GPUs, but we do all have different skills and capabilities.&lt;/p&gt;

&lt;p&gt;It is a managers role, or that of a self managing team, to act like the scheduler in a software system, to allocate tasks to the relevant individuals or teams.&lt;/p&gt;

&lt;hr /&gt;

&lt;p&gt;I hope that thinking about these familiar concepts in different ways may help us appreciate the nuances of both, and may be also gain a bit of understanding for responsibilities that might be outside our own realm.&lt;/p&gt;
</description>
                <pubDate>Thu, 03 Nov 2016 00:00:00 +0000</pubDate>
                <link>https://www.hidefsoftware.co.uk/2016/11/03/management-as-multithreading-part-2/</link>
                <guid isPermaLink="true">https://www.hidefsoftware.co.uk/2016/11/03/management-as-multithreading-part-2/</guid>
                
                <category>management</category>
                
                <category>agile</category>
                
                <category>multithreading</category>
                
                
            </item>
        
            <item>
                <title>Management As Multithreading</title>
                <description>&lt;blockquote&gt;
  &lt;p&gt;What if developers and managers can be models using the same concepts as in multi-threaded software?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;As developers we often talk about limiting work in progress. The reasons for this are well understood thanks to books like [The Phoenix Project](https://en.wikipedia.org/wiki/The_Phoenix_Project_(novel%29) and [The Goal](https://en.wikipedia.org/wiki/The_Goal_(novel%29) before it. As a developer I notice that this is not entirely unlike a single thread in my software doing a single piece of work at any one time.&lt;/p&gt;

&lt;!--more--&gt;

&lt;p&gt;Obviously, in an organisation of any size, you are going to have many individuals in many teams all working towards different goals, and so you must have multiple work streams, even a single work stream is only working on one task at a time. This is a parallel for multithreading; increasing through put by increasing the number of channels available to do work.&lt;/p&gt;

&lt;p&gt;Some of the roles of management in an organisation are to ensure that different work streams:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Have work to do&lt;/li&gt;
  &lt;li&gt;Have access to the resources they need&lt;/li&gt;
  &lt;li&gt;Timelines align with those of other related teams&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If we apply the idea of multi-threading to the space of work stream management, we might find some insight into how teams might work better, and work better together.&lt;/p&gt;

&lt;h2&gt;Concepts&lt;/h2&gt;

&lt;h3&gt;Forking/Joining&lt;/h3&gt;

&lt;p&gt;When a program reaches a point where work can be executed concurrently, multiple threads can be created to perform this work concurrently. After work has been done on multiple threads, the program will reach a point where all concurrent work must have been completed before the program may continue.&lt;/p&gt;

&lt;p&gt;When a stream of work reaches a point where multiple tasks may be conducted in parallel, e.g. multiple clients must consume the same service, and completion of these tasks must be synchronised, e.g. for a product launch, the manager is essentially forking threads of working, waiting for them to complete and joining these threads again before continuing execution of the piece of work.&lt;/p&gt;

&lt;p&gt;From this analogy it seems that it might be worth an experiment in visualising work streams as threads complete with forks and joins so that dependencies might be made visible to the streams involved,and so that the limited number of works streams available (as in a limited number of available threads) can be used to show when work is able to be executed concurrently but cannot be due to other constraints, such as lack of a team/individual to conduct the work until other work has been completed.&lt;/p&gt;

&lt;h3&gt;Locks&lt;/h3&gt;

&lt;p&gt;When two threads are trying to operate on the same resource, we often use locks to ensure that they do not interfere with each other. This means that the act of waiting for a resource to become available, be it a response from a request made to another thread or simply the right to access that same resource is a blocking operation. This mirrors the problem of workstreams being blocked when waiting for dependent tasks and resources to become available. Obviously steps are taken to ensure that the team does not remain blocked. Parking a task and continuing on another is a form of asynchronous work.&lt;/p&gt;

&lt;h3&gt;Preemptive Multi tasking&lt;/h3&gt;

&lt;p&gt;This is probably the most common form of multitasking as an individual worker. You work on a task until someone interrupts you and asks you to do something else. This &lt;em&gt;does&lt;/em&gt; allow you to be highly responsive as other parties do not have to wait for you to finish your task before interacting, but both humans and computer must context switch every time they are interrupted. For computers this overhead is so minor it’s barely even considered, but for humans it is a huge inconveinence and frustration. One of the biggest complaints I hear around the office is “I keep being interrupted, I can’t get any work done”. While this model works well in software for greatly increased flexibility, the trade off of context switching makes this a very painful way of managing concurrent tasks.&lt;/p&gt;

&lt;h3&gt;Cooperative Multi tasking&lt;/h3&gt;

&lt;p&gt;In contrast to preemptive multi-tasking, cooperative multi-tasking allows the code or the worker to break multiple long running tasks down into smaller chunks, so that when an individual chunk has been completed, a chunk from a different task can be conducted. This has several parallel switch agile software delivery. Small work items allow management (or the coordinating code) to choose its work dynamically rather than commiting to long periods without the ability to react and change direction. In our work, this may be a change of business or technical direction, in software this may be the decision to stop enumerating a complex data source. This model sacrifices the ability to interrupt a task, also reduces the obstacle of context switching. As humans we are also inclined to be happier about context switching when we have got to a natural break in the work.&lt;/p&gt;

&lt;p&gt;The lack of any ability to interrupt tasks can be a problem in software and in our work. Runaway threads/tasks or tasks that have become blocked and never unblocked must be managed. Cooperating multitasking offers you nothing in this area; so while it is a much easier way for us humans to work it is useful to keep preemptive multi-tasking as a weapon on your arsenal in the unfortunate instance that a task or thread gets out of control.&lt;/p&gt;

&lt;hr /&gt;

&lt;p&gt;I have explored more of these ideas in &lt;a href=&quot;/2016/11management-as-multithreading-part-2&quot;&gt;Part 2&lt;/a&gt;.&lt;/p&gt;
</description>
                <pubDate>Wed, 02 Nov 2016 00:00:00 +0000</pubDate>
                <link>https://www.hidefsoftware.co.uk/2016/11/02/management-as-multithreading/</link>
                <guid isPermaLink="true">https://www.hidefsoftware.co.uk/2016/11/02/management-as-multithreading/</guid>
                
                <category>management</category>
                
                <category>agile</category>
                
                <category>multithreading</category>
                
                
            </item>
        
            <item>
                <title>Event Handling In Games</title>
                <description>&lt;p&gt;Being predominantly an applications programmer, I have a different take on things that people in the games development area might. Event handling is something that one might do quite differently in many different situations. Here are the ideas I’ve had and the pros and cons.&lt;/p&gt;

&lt;!--more--&gt;

&lt;h2&gt;Callbacks&lt;/h2&gt;
&lt;p&gt;Provided by the language. C#, nowadays, makes extensive use of &lt;strong&gt;Funcs&lt;/strong&gt;, &lt;strong&gt;Actions&lt;/strong&gt; and provides &lt;strong&gt;Lambdas&lt;/strong&gt; to let you do it. Lots of other languages do this too. Even though this is a new concept in some of the higher level languages, only arriving in C# in 2003, C has had this for donkeys years. With C++ probably being the biggest gamedev language this option is widely available for most gamedevs for a long long time, however I have rarely seen it used.&lt;/p&gt;

&lt;h2&gt;Messages&lt;/h2&gt;
&lt;p&gt;A more application oriented than game oriented way of handling the problem would be to send messages around the &lt;strong&gt;game&lt;/strong&gt;, be they objects or strings or hash tables which contain the relevant information. This gives us the option to &lt;strong&gt;trigger an event&lt;/strong&gt; and then not care about when or where or how it happens. The advantage in application programming is that you don’t need to worry about handling the event, other code will. Great for chopping things up, but I have never seen this approach used in a game, &lt;strong&gt;EVER&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;Ad Hoc Handling&lt;/h2&gt;
&lt;p&gt;The most common use of event handling that I have seen in games, is just handling it as it comes. I understand that this is nice and simple and logical to have your update method identify that some work needs doing and doing it. But:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;This buries your events deep in your update method&lt;/li&gt;
  &lt;li&gt;It can make your update method a huge and unwieldy mass of code&lt;/li&gt;
  &lt;li&gt;It forces you to process events &lt;strong&gt;AS&lt;/strong&gt; they happen&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;In a simple game this is probably the easiest option as you don’t have to think about it to go down this route. However as your game grows, you will be tied to doing &lt;strong&gt;ALL&lt;/strong&gt; event work in any given update method, regardless of whether it requires immediate attention or not.&lt;/p&gt;

&lt;p&gt;I would probably put myself in favour of a &lt;strong&gt;Messaging approach&lt;/strong&gt;, especially if you are doing any networking. If you can make your game a system which passes messages around from component to component, then passing messages to a component on a remote system via networking is nothing new.&lt;/p&gt;

&lt;p&gt;As such, the next game that I work on I intend to approach it as a messaging exercise to see if it as easy as I suspect to transition from a message based game engine to a remote-messaging based game engine (networking).&lt;/p&gt;

&lt;p&gt;Does anybody have a different view on it? Does anybody do anything else?&lt;/p&gt;

&lt;hr /&gt;

&lt;p&gt;Comments to: http://www.reddit.com/r/gamedev/comments/rj9mf/event_handling_in_games/ please.&lt;/p&gt;
</description>
                <pubDate>Thu, 29 Mar 2012 00:00:00 +0000</pubDate>
                <link>https://www.hidefsoftware.co.uk/2012/03/29/event-handling-in-games/</link>
                <guid isPermaLink="true">https://www.hidefsoftware.co.uk/2012/03/29/event-handling-in-games/</guid>
                
                <category>gamedev</category>
                
                
            </item>
        
    </channel>
</rss>