<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">

    <title>FINN Technology</title>
    <link rel="self" type="application/atom+xml" href="https://tech.finn.nofeed/"/>
    <link rel="alternate" type="text/html" href="https://tech.finn.no"/>
    <updated>2020-04-08T09:06:30+00:00</updated>
    <id>https://tech.finn.no</id>
    <author>
        <name>Finn Technology</name>
        <email>tech@finn.no</email>
    </author>

    
    <entry>
        <title>Haskell at FINN.no</title>
        <link rel="alternate" type="text/html" href="https://tech.finn.no2018/10/18/haskell-at-finn-no/"/>
        <updated>2018-10-18T09:00:00+00:00</updated>
        <id>https://tech.finn.no/2018/10/18/haskell-at-finn-no</id>
        <content type="html">&lt;h3 id=&quot;favorites&quot;&gt;Favorites&lt;/h3&gt;
&lt;p&gt;FINN has this feature where users can mark classified ads as a favorite, making it easy to come back to certain ads later. The feature has been around quite a while, and the systems involved, both frontend and backend were becoming quite hard to maintain. So the AdView-team set out to redesign the whole stack. This blog post will focus on the backend API, which is written in Haskell.&lt;/p&gt;

&lt;h3 id=&quot;haskell&quot;&gt;Haskell&lt;/h3&gt;
&lt;p&gt;Haskell is a purely functional programming language, with a powerful type system. The ability to express intent using types brings correctness, and the composition of a large program as small, independent building blocks makes it easy to reason about the code.&lt;/p&gt;

&lt;p&gt;A large ecosystem of production grade libraries are available from &lt;a href=&quot;https://hackage.haskell.org/&quot;&gt;Hackage&lt;/a&gt;. We make use of Servant (API), Aeson (JSON), Hasql (postgres) and many more. Servant is a type-level API, meaning the “sum” of all our endpoints become a distinct type. This, in turn, means that
refactoring our API gives us heavy compiler assistance. More than once during development, we had the need to do major changes in the endpoints (verbs, paths, request body, responses) and found that once every compilation error was resolved, the new API was working correctly.&lt;/p&gt;

&lt;p&gt;To manage external packages, build and test code, we use &lt;a href=&quot;https://haskellstack.org&quot;&gt;Stack&lt;/a&gt;, because of its familiarity to maven, gradle and sbt-users. &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;stack build&lt;/code&gt; will compile the project, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;stack test&lt;/code&gt; run all the tests. Stack pulls down &lt;a href=&quot;https://www.haskell.org/ghc/&quot;&gt;Glasgow Haskell Compiler (“GHC”)&lt;/a&gt; along with required libraries. It also supports building and running of the final program in Docker.&lt;/p&gt;

&lt;p&gt;Now Docker is important, because of the cloud infrastructure in FINN; any technology running in Docker can be used as a new Micro Service.&lt;/p&gt;

&lt;h3 id=&quot;experiences&quot;&gt;Experiences&lt;/h3&gt;
&lt;p&gt;So what are the downsides to using Haskell? Well - there is really only one. We have to have a certain amount of developers, at least in our team, know Haskell. We meet this challenge in three ways. Firstly by doing a Haskell course internally at FINN. 21 of our developers have signed up for an &lt;a href=&quot;https://www.futurelearn.com/courses/functional-programming-haskell/&quot;&gt;Introductory Haskell Course from the University of Glasgow&lt;/a&gt;, secondly we cheated a little by simply recruiting another Haskell developer. And lastly, we will put some effort into the &lt;a href=&quot;https://www.meetup.com/Oslo-Haskell/&quot;&gt;Oslo Haskell Meetup group&lt;/a&gt;, hopefully spawning even more Haskellers.&lt;/p&gt;

&lt;p&gt;Really? No more downsides? Well, it should be mentioned that our build-times on Travis CI (self-hosted) are not super-awesome-great. We are currently looking at 8-9 minutes for a complete build (including integration tests). The Stack build tool uses Docker for every task, and some times needs to pull a new version of an image. This hurts build times, but we can live with this as it gives us benefits in terms of isolation.&lt;/p&gt;

&lt;p&gt;As for performance, the &lt;a href=&quot;https://hackage.haskell.org/package/warp&quot;&gt;Warp web server&lt;/a&gt; is doing an excellent job of spawning lightweight threads and keeping CPU and memory usage low. An indication of memory and CPU usage is given below. Note that the old API still have way more traffic, so a direct comparison is very unfair!&lt;/p&gt;

&lt;figure&gt;
    &lt;img class=&quot;center-block&quot; src=&quot;/images/2018-10-18-haskell-at-finn-no/performance.png&quot; alt=&quot;haskell performance as seen by kubernetes&quot; title=&quot;haskell performance as seen by kubernetes&quot; /&gt;
    &lt;figcaption style=&quot;text-align:right; font-style:italic;&quot;&gt;&lt;strong&gt;favorite-api&lt;/strong&gt; is the new API (Haskell).&lt;br /&gt;&lt;strong&gt;classified-favorite-management-server&lt;/strong&gt; is the old API (Java, hence the long name).&lt;br /&gt;You can barely see the &lt;strong&gt;32 MB&lt;/strong&gt; memory footprint of the new API in the graphs!&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;h3 id=&quot;check-it-out&quot;&gt;Check it out&lt;/h3&gt;
&lt;p&gt;We are really looking forward to putting more load on our new API, and doing more Haskell in the future.
You can check out the redesign of &lt;a href=&quot;https://www.finn.no/favoritter&quot;&gt;favorites&lt;/a&gt; for yourself.
And if you would like to learn some Haskell, a great starting point is &lt;a href=&quot;http://learnyouahaskell.com/&quot;&gt;Learn You A Haskell For Great Good&lt;/a&gt;&lt;/p&gt;
</content>
        
        <author>
            <name>Sjur Millidahl</name>
            
            <email></email>
            
        </author>
        
    </entry>
    
    <entry>
        <title>Preparing your product for machine learning</title>
        <link rel="alternate" type="text/html" href="https://tech.finn.no2018/08/09/preparing-your-product-for-ml/"/>
        <updated>2018-08-09T08:00:00+00:00</updated>
        <id>https://tech.finn.no/2018/08/09/preparing-your-product-for-ml</id>
        <content type="html">&lt;p&gt;At many companies, there are few data scientists and many projects which may be very interesting for the business to
apply machine learning on. For most of these companies, starting greenfield projects with a data scientist on the team
might be either a difficult political battle (to get assigned resources) or impossible, if there are no data scientists yet.&lt;/p&gt;

&lt;p&gt;But how can a team prepare their project for the day when there will be a data scientist
around, and in the process, maybe improve their current product as well?&lt;/p&gt;

&lt;p&gt;In &lt;a href=&quot;https://developers.google.com/machine-learning/guides/rules-of-ml/&quot;&gt;Google’s “Rules of ML”&lt;/a&gt;,
they make some good suggestions for this in their introduction:&lt;/p&gt;
&lt;blockquote&gt;
  &lt;p&gt;Do machine learning like the great engineer you are, not the great machine learning expert you aren’t.
Most of the problems you will face, are in fact engineering problems. Most of the gains come from great features, not great machine learning algorithms.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This means, the team already has great potential to improve their product in ways that the data
scientist might help them do in a more structured way later. I believe as long as the engineers and programmers
know how machine learning algorithms like having the data shaped, they can take advantage of this to move forward confidently.&lt;/p&gt;

&lt;h2 id=&quot;what-kind-of-data-does-a-machine-learning-algorithm-like&quot;&gt;What kind of data does a machine learning algorithm like?&lt;/h2&gt;
&lt;p&gt;Computers like things that are quantifiable by numbers, and this is probably obvious to many programmers,
but it’s easy to forget this when we have tools like Google that can find and seemingly understand what we want just by typing a couple of words.&lt;/p&gt;

&lt;p&gt;According to a Kaggle survey from 2017, &lt;a href=&quot;https://www.theverge.com/2017/11/1/16589246/machine-learning-data-science-dirty-data-kaggle-survey-2017&quot;&gt;“dirty data” is the biggest problem faced by machine learning practitioners&lt;/a&gt;.
This means data being unstructured in some form, either by being severely skewed in one direction or the other, being
full of holes (only subsets of users even have a given feature), or data that has to go through processing before
actually becoming a usable feature. Making sure that this friction is minimized is important later for the progress
of machine learning functionality, but it also enables teams to make more use of heuristics right away if the data
is better structured.&lt;/p&gt;

&lt;p&gt;So, what’s important to keep in mind when tracking or saving user information that you plan on using to predict their behavior?&lt;/p&gt;

&lt;h3 id=&quot;is-the-data-numerical&quot;&gt;Is the data numerical?&lt;/h3&gt;
&lt;p&gt;Again, this cannot be understated, &lt;strong&gt;computers like numbers&lt;/strong&gt;. So if at all possible, allow users to input data such as:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;Q: “How many years have you studied at university?” A: “4”&lt;/li&gt;
  &lt;li&gt;Q: “How much do you like cookies?” A: “0-100”&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is easily malleable, and very easy to interpret for ML algorithms.&lt;/p&gt;

&lt;h3 id=&quot;is-the-data-categorical&quot;&gt;Is the data categorical?&lt;/h3&gt;
&lt;p&gt;If you can’t make your data into a number, the second best thing is to make it into a category,
which can be almost like a number from a computer’s perspective.
Turning our previous numerical example into a category could look something like this&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;Q: “What is your highest achieved degree from University/College?” A: “Bachelor’s”.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;From the ML model’s perspective,
we can simply represent each category as a number. For example, if we have “Bachelor’s, Master’s and Ph.D.”
as possible category answers here, it would be representable by “0, 1, 2” if we keep these ordered in the same way going forward.&lt;/p&gt;

&lt;p&gt;As long as you properly restrict your categories, and provide some nice UI/UX for the user to interact with, this will
do just as well as a numerical approach. Just don’t let users type in whatever they like as their categorical answer,
such as “Bachelor” “B.A.”, “About f0ur years” etc, as this will doom you to forever clean up data afterwards before using it.
(if you think the last option was a joke and didn’t fit the question at all, you’re right, but users will eventually write something similar)&lt;/p&gt;

&lt;h4 id=&quot;ui---making-categoricalnumerical-inputs-work&quot;&gt;UI - Making categorical/numerical inputs work&lt;/h4&gt;
&lt;p&gt;There are some very simple, yet powerful things you can do with your UI to facilitate the usage of categorical variables.&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;Use auto-complete for your inputs, instead of free text inputs. This allows the user to write in a natural way, but only select from the subset you have defined.&lt;/li&gt;
  &lt;li&gt;Use sliders for scales from 0-X for numerical inputs.&lt;/li&gt;
  &lt;li&gt;Allow the user to connect via an integration to some external service to retrieve metrics if possible.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;can-we-process-the-data-to-make-it-numerical&quot;&gt;Can we process the data to make it numerical?&lt;/h3&gt;
&lt;p&gt;This mostly involves using &lt;a href=&quot;https://en.wikipedia.org/wiki/Natural_language_processing&quot;&gt;NLP(Natural Language Processing)&lt;/a&gt;
to process text information, and &lt;a href=&quot;https://en.wikipedia.org/wiki/Computer_vision&quot;&gt;CV(Computer Vision)&lt;/a&gt;
to process images. For most teams and organizations this is more of a last resort, as it involves an extra layer of
complexity, required competency and up-front work before actually starting to make use of the data.&lt;/p&gt;

&lt;figure&gt;
    &lt;img class=&quot;center-block&quot; src=&quot;/images/2018-08-09-preparing-your-product-for-ml/object_detection.jpg&quot; alt=&quot;object detection&quot; title=&quot;object detection&quot; /&gt;
    &lt;figcaption style=&quot;text-align:right; font-style:italic;&quot;&gt;Object detection in action&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;Image recognition is getting to the point of almost being able to recognize anything in a picture that you yourself
can recognize in it, such as “Is there an orange in this picture?”, “how many people are in the image?” and
“how are the people standing?”. NLP however still has a ways to go before interpreting texts in the same way as humans,
so for most teams I would highly advise against this.&lt;/p&gt;

&lt;p&gt;If you’re thinking about adding a text field because it might give you good heuristics to model your
users behavior with normal programming, or allow something to be searched, remember that it can probably
not be used in machine learning. Which means you’ll have to redo the work of capturing that feature some other way, and your data scientist might be sad.&lt;/p&gt;

&lt;h2 id=&quot;good-to-go&quot;&gt;Good to go?&lt;/h2&gt;
&lt;p&gt;As long as you can shape your data into something like this, you are pretty much good to go in terms to passing
the data to a machine learning algorithm. Keep in mind that you should already have a close relationship with which
 indicators you are hoping to improve on as well. This could mean instrumenting the collection of
 data for things such as click through rate, revenue or number of conversions. This will help the data scientist
 that comes on board (and your team) to easily gain confidence in that ML is the right thing for you.
 For some teams, plain old programming might be enough, and better (depending on your needs / resources / data availability).&lt;/p&gt;

&lt;h2 id=&quot;a-few-caveats&quot;&gt;A few caveats&lt;/h2&gt;
&lt;p&gt;Also remember you need to have enough data to be able to tell something about the true distribution of what you’re trying to describe.
If I have the sales prices for 3 Hondas, and I’m trying to predict the estimated sales price of a &lt;a href=&quot;https://en.wikipedia.org/wiki/Honda_CR-V&quot;&gt;Honda CR-V 2015 edition&lt;/a&gt;,
I hardly have enough information to reliably tell what it should be worth. Here you might find traditional
statistics helpful to figure out when you have enough data. However if you have millions upon millions of examples
of something, usually you should be fine, no matter what you’re trying to figure out about the world.&lt;/p&gt;

&lt;p&gt;Another smart thing to keep an eye out for is, “Can you yourself execute the task of what you want ML to do for you,
given your available data?” (on a small sub-sample of course).
If I have only the brand name, and edition year for the Honda that I’m trying to predict the proper sales price for,
I probably don’t have enough data, as most people are interested in the milage of the car, what extras are included, color of the paint-job, and so on.&lt;/p&gt;

&lt;p&gt;Is the feature you have selected affected by time? Then you might need more complex models to model it, and proper time-keeping at the data-collection stage.&lt;/p&gt;

&lt;p&gt;Is the feature affected only by you, and nobody else? Or is it dependent on the way your UI works,
how another team tracks info to their database, or is it depending on something your other models already affects?
These are important things to keep in mind before drawing too decisive conclusions about what the data tells you, and might also bias your model later.&lt;/p&gt;

&lt;p&gt;And how much data should a team save? I would say, save as much as you can without annoying users and trampling privacy laws such as GDPR.
It’s a well known fact that &lt;a href=&quot;https://youtu.be/yvDCzhbjYWs?t=551&quot;&gt;more data, means better results in the long run.&lt;/a&gt;
Just don’t reduce the user experience to collect data you don’t know you’ll need later.
Don’t set out to track everything about users right away dismissively thinking that as long as you have the data, AI can solve all your problems later.
Remember to make this an iterative process your team or domain experts are a part of.&lt;/p&gt;

&lt;h2 id=&quot;in-conclusion&quot;&gt;In conclusion&lt;/h2&gt;
&lt;p&gt;Normal programming can usually get you close to your goal, or even completely to your goal depending
on what you want to achieve. However even if you end up replacing parts of your approach later when you get a data scientist,
it is not in vain as you will have discovered useful features, and have a mountain of data the data scientist can sift through.&lt;/p&gt;

&lt;p&gt;Just remember to format the data in a way which makes it easily usable by computers,
and keep track of the metrics you want to improve ahead of time! Without this, even the normal programming approach will be hampered.&lt;/p&gt;

&lt;p&gt;If this is something that interests you, or that your team is struggling with
I recommend checking out &lt;a href=&quot;https://developers.google.com/machine-learning/guides/rules-of-ml/&quot;&gt;Rules of ML by Google authors&lt;/a&gt;
and/or &lt;a href=&quot;https://petewarden.com/2018/05/28/why-you-need-to-improve-your-training-data-and-how-to-do-it/&quot;&gt;Why you need to improve your training data by Pete Warden&lt;/a&gt;.
Facebook even dedicated a purely to data to it in &lt;a href=&quot;https://research.fb.com/the-facebook-field-guide-to-machine-learning-video-series/&quot;&gt;their new machine learning field guide&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Also, if you’re interested in machine learning and programming, I usually tweet whatever interesting stuff I stumble upon at &lt;a href=&quot;https://twitter.com/JoakimRi&quot;&gt;@JoakimRi&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Thanks to &lt;a href=&quot;https://twitter.com/thorkilds&quot;&gt;Thorkild Stray&lt;/a&gt; and &lt;a href=&quot;https://twitter.com/simeneide&quot;&gt;Simen Eide&lt;/a&gt; for reviewing earlier drafts of the post, and providing feedback!&lt;/p&gt;
</content>
        
        <author>
            <name>Joakim Rishaug</name>
            
            <email></email>
            
        </author>
        
    </entry>
    
    <entry>
        <title>5 learnings from Hack Days in FINN</title>
        <link rel="alternate" type="text/html" href="https://tech.finn.no2018/06/28/hack-days-in-finn/"/>
        <updated>2018-06-28T08:00:00+00:00</updated>
        <id>https://tech.finn.no/2018/06/28/hack-days-in-finn</id>
        <content type="html">
</content>
        
        <author>
            <name>Vegard Storstad</name>
            
            <email></email>
            
        </author>
        
    </entry>
    
    <entry>
        <title>Personalized search with a custom Solr plugin</title>
        <link rel="alternate" type="text/html" href="https://tech.finn.no2018/04/10/personalized-search/"/>
        <updated>2018-04-10T06:00:00+00:00</updated>
        <id>https://tech.finn.no/2018/04/10/personalized-search</id>
        <content type="html">&lt;h2 id=&quot;background&quot;&gt;Background&lt;/h2&gt;

&lt;p&gt;On &lt;a href=&quot;https://www.finn.no&quot;&gt;FINN.no&lt;/a&gt; people can search for classified ads, where the backend system is using Solr as the search engine. Default sorting on the vertical “torget” is by relevancy, which is based on Solr score. The Solr score for a document is again calculated from query relevance and the ad’s published date. Here is an example searching for the word chair:&lt;/p&gt;

&lt;figure&gt;
    &lt;img class=&quot;center-block&quot; src=&quot;/images/2018-04-10-personalized-search/example_without_personalization2.png&quot; alt=&quot;alt&quot; title=&quot;Non-personalized search&quot; /&gt;
    &lt;figcaption style=&quot;text-align:right; font-style:italic;&quot;&gt;Non-personalized search&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;The first and third ad are bought positions, while the rest are sorted by published time and the importance of the word chair (Norwegian: stol).&lt;br /&gt;&lt;/p&gt;

&lt;p&gt;In the fall of 2017, we started experimenting with ways to improve the relevancy sorting. Our first try was by boosting geo distance, ads close to my position would get a higher score.
But we could not see any positive changes for our product KPIs. Then we wanted to try sort by mixing scores from both Solr and our recommendation system.
Our recommendation system already had an api where we could send a user id and a list of ad ids, and receive a recommendation score for each of the ads.&lt;/p&gt;

&lt;h2 id=&quot;a-new-solution&quot;&gt;A new solution&lt;/h2&gt;

&lt;p&gt;We evaluated a few different solutions, mainly:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;learning to rank in Solr&lt;/li&gt;
  &lt;li&gt;custom Solr plugin using the existing recommendations api&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Since we already had a system for recommendations, and awesome data scientists tuning the algorithms and so on, we chose to test the latter. 
An uncertainty was if we could get the response times needed for a search.&lt;/p&gt;

&lt;h3 id=&quot;solr-searchcomponent&quot;&gt;Solr SearchComponent&lt;/h3&gt;
&lt;p&gt;A Solr search component contains several phases used by the search handler. As we do not use sharded indices for the search we wanted to test, these are the important phases:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;prepare - Preparing the response -&amp;gt; parsing request parameters&lt;/li&gt;
  &lt;li&gt;process - Processing the request for the current component&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;requirements&quot;&gt;Requirements&lt;/h3&gt;
&lt;ul&gt;
  &lt;li&gt;the personalization score should influence the order of the search result&lt;/li&gt;
  &lt;li&gt;we want to easily be able to change recommendation algorithm, and test a new one against the current search (with or without personalization)&lt;/li&gt;
  &lt;li&gt;we also wanted to tune Solr score/recommendation score balance&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We therefore added these parameters:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;personalization (Boolean) - to switch on/off the personalization search&lt;/li&gt;
  &lt;li&gt;recommender id (String) - to set recommendation algorithm&lt;/li&gt;
  &lt;li&gt;rerank pages (Integer) - number of pages with personalization per search, typically the first 5 or 10 pages will be personalized. This is to increase performance, compared to calculating personalization scores for every document in a search result.&lt;/li&gt;
  &lt;li&gt;personalization score weight (Double) - recommendation score weight, 1= equal weight to Solr score&lt;/li&gt;
  &lt;li&gt;user id (String) - to be able to personalize the search&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The solution we chose for the plugin is:&lt;br /&gt;
1) Let the search fetch all documents from the first x pages, set by the parameter rerank_pages.&lt;br /&gt;
2) Fetch recommendation score for each document returned by the search from the recommendations api.&lt;br /&gt;
3) Sort the documents by a combination of Solr score and recommendation score.&lt;br /&gt;
4) Return a subset of the search result, based on offset and rows parameters&lt;/p&gt;

&lt;p&gt;We want the search from the default QueryComponent to run before the PersonalizationComponent, since we need the ad ids (Solr document ids) for the first x pages of the search result. Therefore we setup our component as a last-component in the Solrconfig.&lt;/p&gt;

&lt;div class=&quot;language-xml 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;nt&quot;&gt;&amp;lt;requestHandler&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;name=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;dismax&quot;&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;class=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;no.finntech.search.Solr.searchhandler.SearchHandler&quot;&lt;/span&gt; 
    &lt;span class=&quot;na&quot;&gt;default=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;true&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;arr&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;name=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;last-components&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;str&amp;gt;&lt;/span&gt;personalization&lt;span class=&quot;nt&quot;&gt;&amp;lt;/str&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;/arr&amp;gt;&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;&amp;lt;/requestHandler&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;searchcomponentprepare&quot;&gt;SearchComponent.prepare&lt;/h3&gt;

&lt;p&gt;The prepare step are evaluating and setting the search parameters count, offset and score.&lt;/p&gt;

&lt;div class=&quot;language-java 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;nd&quot;&gt;@Override&lt;/span&gt;
&lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;prepare&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;ResponseBuilder&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;rb&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;throws&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;IOException&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
   &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;(!&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;shouldWePersonalize&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;rb&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;req&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;))&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
       &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
   &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;

   &lt;span class=&quot;kd&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;SolrParams&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;params&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;rb&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;req&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;getParams&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;();&lt;/span&gt;
   &lt;span class=&quot;kd&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;SortSpec&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;sortSpec&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;rb&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;getSortSpec&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;();&lt;/span&gt;
   &lt;span class=&quot;kd&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;offset&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;sortSpec&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;getOffset&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;();&lt;/span&gt;
   &lt;span class=&quot;kd&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;reRankNum&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;getReRankNum&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;params&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;

   &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;reRankNum&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;offset&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;//reranking only the first x (reRankPages) pages&lt;/span&gt;
       &lt;span class=&quot;n&quot;&gt;sortSpec&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;setCount&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;reRankNum&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
       &lt;span class=&quot;n&quot;&gt;sortSpec&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;setOffset&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
       &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;params&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;score&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;c1&quot;&gt;//adds score for the search&lt;/span&gt;
           &lt;span class=&quot;n&quot;&gt;rb&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;setFieldFlags&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;SolrIndexSearcher&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;GET_SCORES&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
       &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
   &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;First we are checking the parameters if it is a personalization search. This component will not manipulate a search without personalization.&lt;/p&gt;

&lt;div class=&quot;language-java 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;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;(!&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;shouldWePersonalize&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;rb&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;req&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;))&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
   &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The personalization search will reorder the first x pages, set by the parameter rerank_pages, therefore count and offset is set to include the first x pages of the search result.&lt;/p&gt;

&lt;div class=&quot;language-java 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;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;reRankNum&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;offset&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;//reranking only the first x (reRankPages) pages&lt;/span&gt;
           &lt;span class=&quot;n&quot;&gt;sortSpec&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;setCount&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;reRankNum&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
           &lt;span class=&quot;n&quot;&gt;sortSpec&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;setOffset&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Setting the flag which makes the QueryComponent calculate scores:&lt;/p&gt;

&lt;div class=&quot;language-java 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;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;params&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;score&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;c1&quot;&gt;//adds score for the search&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;rb&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;setFieldFlags&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;SolrIndexSearcher&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;GET_SCORES&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;searchcomponentprocess&quot;&gt;SearchComponent.process&lt;/h3&gt;

&lt;p&gt;In the process step, the recommendation scores are fetched, and the final sorting of the search result are set based on Solr- and recommendations scores.&lt;/p&gt;

&lt;div class=&quot;language-java 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;nd&quot;&gt;@Override&lt;/span&gt;
&lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;process&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;ResponseBuilder&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;rb&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;throws&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;IOException&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
   &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;(!&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;shouldWePersonalize&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;rb&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;req&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;))&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
       &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
   &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
   &lt;span class=&quot;kd&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;SolrParams&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;params&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;rb&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;req&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;getParams&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;();&lt;/span&gt;
   &lt;span class=&quot;kd&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;recommenderId&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;params&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;FINN_PERSONALIZATION_RECOMMENDERID&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
   &lt;span class=&quot;kd&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;userId&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;params&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;FINN_USERID&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
   &lt;span class=&quot;kd&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;rows&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;params&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;getInt&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;ROWS&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
   &lt;span class=&quot;kd&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;offset&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;getOffset&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;params&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
   &lt;span class=&quot;kd&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;float&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;personalizationScoreWeight&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;getPersonalizationScoreWeight&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;params&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
   &lt;span class=&quot;kd&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;reRankNum&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;rb&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;getResults&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;().&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;docList&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;size&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;();&lt;/span&gt;

   &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;reRankNum&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;offset&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;//only reranking first x (reRankNum) documents of a search&lt;/span&gt;
       &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
   &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;

   &lt;span class=&quot;kd&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;DocList&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;initialSearchResult&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;rb&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;getResults&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;().&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;docList&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
   &lt;span class=&quot;nc&quot;&gt;List&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;ScoredAd&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;results&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;getInitialSearchDocs&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;rb&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;reRankNum&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;initialSearchResult&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;

   &lt;span class=&quot;kd&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Map&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;ScoredAd&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;recommendedItems&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; 
        &lt;span class=&quot;n&quot;&gt;getRecommendedItems&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;userId&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;recommenderId&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;reRankNum&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;results&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;

   &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;recommendedItems&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;size&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
       &lt;span class=&quot;n&quot;&gt;results&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;forEach&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;result&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
           &lt;span class=&quot;kd&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;float&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;normalizedSolrScore&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;normalizedSolrScore&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;result&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;getScore&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(),&lt;/span&gt; 
                                                                 &lt;span class=&quot;n&quot;&gt;initialSearchResult&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;maxScore&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;());&lt;/span&gt;
           &lt;span class=&quot;kd&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;float&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;personalizationScore&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;personalizationScore&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;personalizationScoreWeight&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; 
                                                                   &lt;span class=&quot;n&quot;&gt;recommendedItems&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; 
                                                                   &lt;span class=&quot;n&quot;&gt;result&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;getAdId&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;());&lt;/span&gt;
           &lt;span class=&quot;n&quot;&gt;result&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;setScore&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;normalizedSolrScore&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;personalizationScore&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
       &lt;span class=&quot;o&quot;&gt;});&lt;/span&gt;
       &lt;span class=&quot;n&quot;&gt;results&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;sort&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;((&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;r1&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;r2&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Float&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;compare&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;r2&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;getScore&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(),&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;r1&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;getScore&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;()));&lt;/span&gt;
   &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;

   &lt;span class=&quot;kd&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;List&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;ScoredAd&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;searchResult&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;results&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;stream&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;()&lt;/span&gt;
           &lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;skip&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;offset&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
           &lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;limit&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;rows&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
           &lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;collect&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;toList&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;());&lt;/span&gt;
   &lt;span class=&quot;k&quot;&gt;try&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
       &lt;span class=&quot;n&quot;&gt;addToResponse&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;rb&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;reRankNum&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;searchResult&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;initialSearchResult&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
   &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;catch&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;IOException&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;e&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
       &lt;span class=&quot;no&quot;&gt;LOG&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;error&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Could not add recommended items to search result: &quot;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;e&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
   &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;As in the prepare step, we start the process step by escaping non-personalization searches, checking if the search is a personalization search, and if the search is within the pages to personalize:&lt;/p&gt;

&lt;div class=&quot;language-java 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;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;(!&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;shouldWePersonalize&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;rb&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;req&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;))&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
   &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;reRankNum&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;offset&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;//only reranking first x (reRankNum) documents of a search&lt;/span&gt;
   &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The QueryComponent runs its process step before the PersonalizationComponent’s process, and creates a doclist containing the search results, with lucene document ids and scores.&lt;/p&gt;

&lt;div class=&quot;language-java 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;kd&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;DocList&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;initialSearchResult&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;rb&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;getResults&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;().&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;docList&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;nc&quot;&gt;List&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;ScoredAd&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;results&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;getInitialSearchDocs&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;rb&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;reRankNum&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;initialSearchResult&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Note that Lucene document id is not the same as Solr document id. 
Next, to get the Solr document ids, which we will use for recommendation scores, we need to do a index/document cache lookup per document. &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;java searcher.doc(luceneDocumentId, &amp;lt;Set of fields to obtain&amp;gt;)&lt;/code&gt; is one of the more costly operations in this plugin. 
The Lucene document ids will change when anything in the index changes, it is therefore not trivial to cache these lookups.&lt;/p&gt;

&lt;div class=&quot;language-java 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;kd&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;static&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;List&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;ScoredAd&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;getInitialSearchDocs&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;ResponseBuilder&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;rb&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; 
                                                   &lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;reRankNum&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; 
                                                   &lt;span class=&quot;nc&quot;&gt;DocList&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;initialSearchResult&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; 
                                                   &lt;span class=&quot;kd&quot;&gt;throws&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;IOException&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
   &lt;span class=&quot;nc&quot;&gt;List&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;ScoredAd&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;results&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;ArrayList&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&amp;gt;();&lt;/span&gt;
   &lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;counter&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
   &lt;span class=&quot;kd&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;SolrIndexSearcher&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;searcher&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;rb&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;req&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;getSearcher&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;();&lt;/span&gt;
   &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;DocIterator&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;docIterator&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;initialSearchResult&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;iterator&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;();&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;docIterator&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;hasNext&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; 
            &lt;span class=&quot;n&quot;&gt;counter&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;reRankNum&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;counter&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;++)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
       &lt;span class=&quot;n&quot;&gt;results&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;add&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;createDoc&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;searcher&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;docIterator&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;next&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(),&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;docIterator&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;score&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;()));&lt;/span&gt;
   &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
   &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;results&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;kd&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;static&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;ScoredAd&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;createDoc&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;SolrIndexSearcher&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;searcher&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; 
                                  &lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;luceneDocumentId&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; 
                                  &lt;span class=&quot;kt&quot;&gt;float&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;score&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; 
                                  &lt;span class=&quot;kd&quot;&gt;throws&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;IOException&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
   &lt;span class=&quot;kd&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Document&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;doc&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;searcher&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;doc&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;luceneDocumentId&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Sets&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;newHashSet&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;id&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;));&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;//there should be a better way to do the luceneid -&amp;gt; id mapping&lt;/span&gt;
   &lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;adid&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;doc&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;id&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
   &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;ScoredAd&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;luceneDocumentId&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;adid&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;score&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Next we use the recommendation api to get a personalization score for each document. This is a http-request, and is therefore also a high cost part, latency vise, of the plugin. 
Our api takes the following parameters:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;UserId&lt;/li&gt;
  &lt;li&gt;RecommenderId&lt;/li&gt;
  &lt;li&gt;List of AdIds (Solr document ids)&lt;/li&gt;
  &lt;li&gt;MaxRows&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And returns a list of adIds with recommendation score. ScoredAd now contains both the Solr- and the recommendation-score. These are further used for sorting the search results: &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;total score = normalized Solr score + (normalized recommendation score * recommendation score weight)&lt;/code&gt;&lt;/p&gt;

&lt;div class=&quot;language-java 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;kd&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Map&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;ScoredAd&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;recommendedItems&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;getRecommendedItems&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;userId&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; 
                                                                   &lt;span class=&quot;n&quot;&gt;recommenderId&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; 
                                                                   &lt;span class=&quot;n&quot;&gt;reRankNum&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; 
                                                                   &lt;span class=&quot;n&quot;&gt;results&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;recommendedItems&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;size&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
   &lt;span class=&quot;n&quot;&gt;results&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;forEach&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;result&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
       &lt;span class=&quot;kd&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;float&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;normalizedSolrScore&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;normalizedSolrScore&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;result&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;getScore&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(),&lt;/span&gt; 
                                                             &lt;span class=&quot;n&quot;&gt;initialSearchResult&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;maxScore&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;());&lt;/span&gt;
       &lt;span class=&quot;kd&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;float&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;personalizationScore&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;personalizationScore&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;personalizationScoreWeight&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; 
                                                               &lt;span class=&quot;n&quot;&gt;recommendedItems&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; 
                                                               &lt;span class=&quot;n&quot;&gt;result&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;getAdId&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;());&lt;/span&gt;
       &lt;span class=&quot;n&quot;&gt;result&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;setScore&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;normalizedSolrScore&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;personalizationScore&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
   &lt;span class=&quot;o&quot;&gt;});&lt;/span&gt;
   &lt;span class=&quot;n&quot;&gt;results&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;sort&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;((&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;r1&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;r2&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Float&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;compare&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;r2&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;getScore&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(),&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;r1&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;getScore&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;()));&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Finally we are preparing the search response with a subset of the search results, set by offset and rows.&lt;/p&gt;

&lt;div class=&quot;language-java 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;kd&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;List&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;ScoredAd&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;searchResult&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;results&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;stream&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;()&lt;/span&gt;
       &lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;skip&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;offset&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
       &lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;limit&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;rows&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
       &lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;collect&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;toList&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;());&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;try&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
   &lt;span class=&quot;n&quot;&gt;addToResponse&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;rb&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;reRankNum&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;searchResult&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;initialSearchResult&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;catch&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;IOException&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;e&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
   &lt;span class=&quot;no&quot;&gt;LOG&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;error&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Could not add recommended items to search result: &quot;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;e&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;kd&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;static&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;addToResponse&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;ResponseBuilder&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;rb&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; 
                                  &lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;reRankNum&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; 
                                  &lt;span class=&quot;nc&quot;&gt;List&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;ScoredAd&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;resultsToReturn&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; 
                                  &lt;span class=&quot;nc&quot;&gt;DocList&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;docList&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;throws&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;IOException&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
   &lt;span class=&quot;nc&quot;&gt;DocListAndSet&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;docListAndSet&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;buildDocListAndSet&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;reRankNum&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;resultsToReturn&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;docList&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
   &lt;span class=&quot;n&quot;&gt;rb&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;setResults&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;docListAndSet&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;

   &lt;span class=&quot;nc&quot;&gt;BasicResultContext&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;resultContext&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;BasicResultContext&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;rb&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
   &lt;span class=&quot;nc&quot;&gt;SolrQueryResponse&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;rsp&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;rb&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;rsp&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
   &lt;span class=&quot;n&quot;&gt;rsp&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;getValues&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;().&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;removeAll&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;response&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
   &lt;span class=&quot;n&quot;&gt;rsp&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;addResponse&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;resultContext&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;response-times&quot;&gt;Response times&lt;/h2&gt;
&lt;p&gt;We will also look at the response times:&lt;/p&gt;

&lt;p&gt;Response times for 95th percentile. Default search is search without any custom plugins, distribution search is the actual search we are trying to replace with personalization search. 
Distribution search is a search where every xth (typically every 4th) document is a professional seller ad, 
this is performed by doing one search for professional sellers and one for non-professional sellers, and merge them together by adding a professional ad for every xth position.
The actual timings in the 95th percentile doubles from 225ms to 450ms. While the basic search takes around 80ms.&lt;/p&gt;

&lt;figure&gt;
    &lt;img class=&quot;center-block&quot; src=&quot;/images/2018-04-10-personalized-search/95th_percentile_latency.png&quot; alt=&quot;alt&quot; title=&quot;95th percentile latency&quot; /&gt;
    &lt;figcaption style=&quot;text-align:right; font-style:italic;&quot;&gt;95th percentile latency&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;Lets also look at the 99th percentile for response time. We here removed the default search, because of too much noise, because of low traffic. 
The personalization search’s 99th percentile is between 60 to 100 ms slower than the search to be replaced.&lt;/p&gt;

&lt;figure&gt;
    &lt;img class=&quot;center-block&quot; src=&quot;/images/2018-04-10-personalized-search/99th_percentile_latency.png&quot; alt=&quot;alt&quot; title=&quot;99th percentile latency&quot; /&gt;
    &lt;figcaption style=&quot;text-align:right; font-style:italic;&quot;&gt;99th percentile latency&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;h2 id=&quot;search-examples&quot;&gt;Search examples&lt;/h2&gt;

&lt;p&gt;Here is a couple of examples of different user doing the same search at the same time, which will result in a completely different order of the ads:&lt;/p&gt;

&lt;figure&gt;
    &lt;img class=&quot;center-block&quot; src=&quot;/images/2018-04-10-personalized-search/example_with_personalized1.png&quot; alt=&quot;alt&quot; title=&quot;example 1 personalized search&quot; /&gt;
    &lt;figcaption style=&quot;text-align:right; font-style:italic;&quot;&gt;Personalized search with a user interested in antiques&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;figure&gt;
    &lt;img class=&quot;center-block&quot; src=&quot;/images/2018-04-10-personalized-search/example_with_personalized2.png&quot; alt=&quot;alt&quot; title=&quot;example 2 personalized search&quot; /&gt;
    &lt;figcaption style=&quot;text-align:right; font-style:italic;&quot;&gt;Personalized search with a user interested in furnitures for children&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;We are now doing a personalization search for the 5 first pages of the search results. 
Both of the searches includes the same 250 ads on the first 5 pages, but as you can see, the order is completely changed between the two examples.
Published dates and the query relevance decides which subset of the search result to personalize.&lt;/p&gt;

&lt;h2 id=&quot;product-results&quot;&gt;Product results&lt;/h2&gt;
&lt;p&gt;We made a version of the personalization search, which included distribution, to take care of the professional sellers.
The personalized search with distribution were tested against the distribution search for 4 months, increasing the share of users getting personalized search to 50% the last couple of months.
These were the results from the most important KPIs we followed:&lt;/p&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt; &lt;/th&gt;
      &lt;th style=&quot;text-align: center&quot;&gt;Distribution search&lt;/th&gt;
      &lt;th style=&quot;text-align: center&quot;&gt;Personalized search with distribution&lt;/th&gt;
      &lt;th style=&quot;text-align: right&quot;&gt;Difference&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;Share of sessions with ad click&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;59.7%&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;62%&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;+3.9% (2.3% points)&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Time from start of search to ad click&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;63 s&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;61.7 s&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;-2.3% (1.3 seconds)&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Clicks to ads without any contact from buyers, per session&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;2.1&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;2.58&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;+22.9%&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;All of these were better with the personalized search, in addition our other KPIs were unchanged.&lt;/p&gt;

&lt;h2 id=&quot;summary&quot;&gt;Summary&lt;/h2&gt;
&lt;p&gt;To understand what kind of data is populated in each of the SearchComponent’s steps, a lot of debugging the default Solr components was necessary, because of incomplete documentation.&lt;/p&gt;

&lt;p&gt;The personalization search makes our search better for our users in FINN.no.
But it does indeed costs more in terms of latency. Here we hope to be able to increase the performance a bit. There are several possibilities:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;tune the document cache / index commits&lt;/li&gt;
  &lt;li&gt;cache recommendation matrices within the Solr servers, to avoid network overhead&lt;/li&gt;
  &lt;li&gt;find a better way to get the Solr document ids&lt;/li&gt;
  &lt;li&gt;hardware resource tuning&lt;/li&gt;
  &lt;li&gt;learning to rank feature in Solr&lt;/li&gt;
  &lt;li&gt;…and so on&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We will still tune our recommendations algorithms and the plugin to get our results even better. 
As of April 2018, all users of the “torget” search gets the recommendation search.&lt;/p&gt;
</content>
        
        <author>
            <name>Henrik Falch</name>
            
        </author>
        
    </entry>
    
    <entry>
        <title>Åpen fagkveld hjemme hos FINN</title>
        <link rel="alternate" type="text/html" href="https://tech.finn.no2018/02/06/fagkveld/"/>
        <updated>2018-02-06T14:14:30+00:00</updated>
        <id>https://tech.finn.no/2018/02/06/fagkveld</id>
        <content type="html">&lt;p&gt;Tradisjonen tro åpner vi også i år dørene hjemme hos oss i Grensen for å dele våre erfaringer rundt hvordan vi jobber med produktutvikling. Teknologi er i fokus, og vi gleder oss til å vise dere hvordan vi bygger - og holder et av Norges mest besøkte nettsteder vedlike.​​​​​​​ En kveld er dedikert studenter, og en de mer erfarne.&lt;/p&gt;

&lt;h3 id=&quot;presentasjoner&quot;&gt;Presentasjoner&lt;/h3&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;/images/2018-02-06-fagkveld-presentasjoner/2018-02-28 Open night - Welcome to FINN presentation - professionals.pdf&quot;&gt;Velkommen&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;/images/2018-02-06-fagkveld-presentasjoner/2018-02-28 GDPR.pdf&quot;&gt;GDPR&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;/images/2018-02-06-fagkveld-presentasjoner/2018-02-28 oppetid.pdf&quot;&gt;Oppetid&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;/images/2018-02-06-fagkveld-presentasjoner/2018-02-28 SocRec.pdf&quot;&gt;Sosial rekruttering&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;/images/2018-02-06-fagkveld-presentasjoner/2018-02-28 Unleash.pdf&quot;&gt;Unleash&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;/images/2018-02-06-fagkveld-presentasjoner/2018-03-07 UU.pdf&quot;&gt;Universell utforming&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;/images/2018-02-06-fagkveld-presentasjoner/2018-03-07 What we look for when we recruit.pdf&quot;&gt;Dette ser vi etter når vi ansetter utviklere&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;/images/2018-02-06-fagkveld-presentasjoner/2018-02-28 Podium.pdf&quot;&gt;Podium&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;åpen-fagkveld-28-februar-kl-16-20-for-de-som-jobber-med-teknologi&quot;&gt;Åpen fagkveld 28. februar kl 16-20 (for de som jobber med teknologi)&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Program&lt;/strong&gt;&lt;/p&gt;

&lt;table&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;16:00&lt;/td&gt;
      &lt;td&gt;&lt;strong&gt;Velkommen til FINN&lt;/strong&gt; ved Nicolai Høge (CTO)&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;16:30&lt;/td&gt;
      &lt;td&gt;&lt;strong&gt;Mat og drikke&lt;/strong&gt;&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;17:00&lt;/td&gt;
      &lt;td&gt;&lt;strong&gt;Sosial rekruttering&lt;/strong&gt; Hvordan vi tok i bruk våre egne data for å lage FINN Jobbmatch.&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;17:30&lt;/td&gt;
      &lt;td&gt;&lt;strong&gt;GDPR&lt;/strong&gt; Samtykke - Hvorfor må vi ha det og hvordan håndterer FINN det i en mikroservices-verden?&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;17:30&lt;/td&gt;
      &lt;td&gt;&lt;strong&gt;Unleash&lt;/strong&gt; Verktøy/rammeverk for A/B testing.&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;18:00&lt;/td&gt;
      &lt;td&gt;&lt;strong&gt;Oppetid&lt;/strong&gt; Hvordan arbeider vi for høy oppetid i FINN. Ambisjoner, målinger og oppfølgning.&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;18:00&lt;/td&gt;
      &lt;td&gt;&lt;strong&gt;Podium&lt;/strong&gt; Frontend i en mikroservices-verden. Hvordan FINN bygger en unison frontend på kryss av microservicer og team med forskjellige eierskap.&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;&lt;img src=&quot;/images/2018-02-06-fagkveld/profaften.jpg&quot; alt=&quot;Fagaften&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;åpen-studentkveld-7-mars-kl-16-20-for-studenter&quot;&gt;Åpen studentkveld 7. mars kl 16-20 (for studenter)&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Program (endringer kan komme)&lt;/strong&gt;&lt;/p&gt;

&lt;table&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;16:00&lt;/td&gt;
      &lt;td&gt;&lt;strong&gt;Velkommen til FINN&lt;/strong&gt; ved Nicolai Høge (CTO)&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;16:30&lt;/td&gt;
      &lt;td&gt;&lt;strong&gt;Mat og drikke&lt;/strong&gt;&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;17:00&lt;/td&gt;
      &lt;td&gt;&lt;strong&gt;Dette ser vi etter når vi ansetter utviklere&lt;/strong&gt;&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;17:30&lt;/td&gt;
      &lt;td&gt;&lt;strong&gt;Sosial rekruttering&lt;/strong&gt; Hvordan vi tok i bruk våre egne data for å lage FINN Jobbmatch&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;17:30&lt;/td&gt;
      &lt;td&gt;&lt;strong&gt;Universell utforming&lt;/strong&gt; - hvorfor og hvordan man skal lage noe som funker for alle. Absolutt ALLE!&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;18:00&lt;/td&gt;
      &lt;td&gt;&lt;strong&gt;Unleash&lt;/strong&gt; Verktøy/rammeverk for A/B testing&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;18:00&lt;/td&gt;
      &lt;td&gt;&lt;strong&gt;Podium&lt;/strong&gt; Frontend i en mikroservices-verden. Hvordan FINN bygger en unison frontend på kryss av microservicer og team med forskjellige eierskap.&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;&lt;img src=&quot;/images/2018-02-06-fagkveld/studentaften.jpg&quot; alt=&quot;Studentaften&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Takk til alle dere som kom.&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;http://jobbifinn.finn.no/&quot;&gt;Karrieresiden&lt;/a&gt; vår inneholder mer info om oss.&lt;/p&gt;
</content>
        
        <author>
            <name>Gunn Skinderviken</name>
            
        </author>
        
    </entry>
    
    <entry>
        <title>Browser statistics December 2017</title>
        <link rel="alternate" type="text/html" href="https://tech.finn.no2017/12/13/browser-statistics-december-2017/"/>
        <updated>2017-12-13T10:02:32+00:00</updated>
        <id>https://tech.finn.no/2017/12/13/browser-statistics-december-2017</id>
        <content type="html">&lt;h1 id=&quot;browser-statistics-december-2017-at-finnno&quot;&gt;Browser statistics, December 2017 at FINN.no#&lt;/h1&gt;

&lt;p&gt;Yuletide is coming real soon now, and we haven’t published browser statistics for 2017 yet! The &lt;a href=&quot;http://tech.finn.no/2016/10/04/browser-statistics-october-2016/&quot;&gt;2016 edition is here&lt;/a&gt; if you want to compare.&lt;/p&gt;

&lt;h2 id=&quot;how-many-visitors-use-a-desktop-or-laptop&quot;&gt;How many visitors use a desktop or laptop?&lt;/h2&gt;

&lt;p&gt;The trend continues, a higher percentage of the traffic is coming from a mobile terminal this year than in 2016, but the trend is that the growth of the curve seems to flatten.&lt;/p&gt;

&lt;p&gt;Our first graph shows the share of our users using a mobile phone, tablet or a desktop computer to access FINN.no, regardless of whether they use our responsive web app (m.finn.no), or a native Android or iPhone app. 72% of our visits are now from a smartphone or a tablet. The traditional desktop/laptop has a market share of 28%.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/2017-12-13/image_0.png&quot; alt=&quot;image alt text&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Which offering/”app” of FINN are people using, regardless of whether the device is a desktop/laptop, mobile or tablet?&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/2017-12-13/image_1.png&quot; alt=&quot;image alt text&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Given that a user is on a mobile or a tablet - are they using the web-version or the app? For the first time the majority of those accessing FINN from a mobile device use our native app - the &lt;a href=&quot;https://www.finn.no&quot;&gt;www.finn.no&lt;/a&gt; share of the visits is down to 48%.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/2017-12-13/image_2.png&quot; alt=&quot;image alt text&quot; /&gt;&lt;/p&gt;

&lt;p&gt;These graphs also shows that we changed the domain name again this spring, so that all web traffic is served from &lt;a href=&quot;https://www.finn.no&quot;&gt;www.finn.no&lt;/a&gt;, and not &lt;a href=&quot;http://m.finn.no&quot;&gt;m.finn.no&lt;/a&gt; any more.&lt;/p&gt;

&lt;h2 id=&quot;browsers-and-devices&quot;&gt;Browsers and devices&lt;/h2&gt;

&lt;p&gt;The browser war is now only a memory of the veterans, but we still have to debug bugs in specific browsers from time to time, and it’s useful to have an idea of which browsers could or should be ignored.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/2017-12-13/image_3.png&quot; alt=&quot;image alt text&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Let’s also look at the which devices are the most popular:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/2017-12-13/image_4.png&quot; alt=&quot;image alt text&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;operating-systems&quot;&gt;Operating systems&lt;/h2&gt;

&lt;p&gt;We’re not using our mobiles all the time, so lte’s take a look at the operating system versions. The changes here go quite slowly, but the longer trend is that Windows 10 is still increasing over Windows 7 and 8.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/2017-12-13/image_5.png&quot; alt=&quot;image alt text&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Distribution of iOS versions - it’s interesting to see how quickly a new version is rolled out and dominating. (Note that iOS 11 did not fit into this graph, but the last week, iOS 11.1.2 was the biggest.)&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/2017-12-13/image_6.png&quot; alt=&quot;image alt text&quot; /&gt;&lt;/p&gt;

&lt;p&gt;…especially compared to Android:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/2017-12-13/image_7.png&quot; alt=&quot;image alt text&quot; /&gt;&lt;/p&gt;

&lt;p&gt;A list fits more elements than a line graph, so here are the top 10 iOS and Android versions the second week of December 2017:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/2017-12-13/image_8.png&quot; alt=&quot;image alt text&quot; /&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/2017-12-13/image_9.png&quot; alt=&quot;image alt text&quot; /&gt;&lt;/p&gt;

&lt;p&gt;There are many models of iPhone, and though we have some doubts about the correctness of these data, this report still gives an indicator.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/2017-12-13/image_10.png&quot; alt=&quot;image alt text&quot; /&gt;&lt;/p&gt;

</content>
        
        <author>
            <name>Henning Spjelkavik</name>
            
        </author>
        
    </entry>
    
    <entry>
        <title>FINN wins Universal Design Award</title>
        <link rel="alternate" type="text/html" href="https://tech.finn.no2017/10/30/finn-wins-universal-design-award/"/>
        <updated>2017-10-30T11:19:07+00:00</updated>
        <id>https://tech.finn.no/2017/10/30/finn-wins-universal-design-award</id>
        <content type="html">&lt;figure&gt;
  &lt;img src=&quot;/images/2017-10-30-finn-wins-universal-design-award/finn_doga_award-2017.jpg&quot; alt=&quot;Family and Equality Minister Solveig Horne handed out the award to FINN employees from the Accessibility Team, Product, Innovation and Technology. From left: Maiken Solberg Olsen, Solveig Horne, Lotte Johansen, Tor-Martin Storsletten, Karina Birkeland Lome, Ingrid Vestby Fredriksen, David Håsäther, Nicolai Høge and John Arne Bjerknes (Jury leader).&quot; /&gt;
  &lt;figcaption class=&quot;b1&quot;&gt;Family and Equality Minister Solveig Horne handed out the award to FINN employees from the Accessibility Team, Product, Innovation and Technology. From left: Maiken Solberg Olsen, Solveig Horne, Lotte Johansen, Tor-Martin Storsletten, Karina Birkeland Lome, Ingrid Vestby Fredriksen, David Håsäther, Nicolai Høge and John Arne Bjerknes (Jury leader).&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;By removing obstacles and involving users, FINN.no has created a service that allows everyone to participate in important social functions. On October 17th we received the &lt;a href=&quot;https://doga.no/dogas-priser/innovasjonsprisen/&quot; target=&quot;_blank&quot;&gt;DOGA Innovation Award for Universal Design&lt;/a&gt; in the Interaction Design and Digital Solutions category. Developers and designers run the grassroots work with accessibility at FINN.no. It started in 2014 when a new accessibility legislation was introduced in Norway. Since then the grassroots group has worked to make accessibility known to everyone who develops FINN.no. Sometimes the work gets hard, and it is extra encouraging to receive such an award. It helps us to show how important it is that we make our services available to everyone.&lt;/p&gt;

&lt;p&gt;FINN.no has received the award for its web solution and corresponding mobile app.&lt;/p&gt;

&lt;h2&gt;House and Work for Everyone&lt;/h2&gt;
&lt;p&gt;It is a major responsibility being the country’s largest digital marketplace. If you do not have access to, for example, the job or real estate market on FINN.no, your opportunities are limited to participate in the community.&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;FINN.no is used by virtually everyone in Norway. Therefore, it is important that people can find a house to live in, a job to go to, trips, or other items they may need, regardless of any disabilities. Through user tests we have learned that where people with disabilities are struggling, others also tend to struggle. By removing obstacles, the solutions become better for everyone.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Images and icons are widely used in our online solution. Designs and texts are made to be as intuitive as possible, among other things by using self-completing text. The FINN.no app is adapted for use by screen readers, which are often used by blind and partially sighted people. The app easily integrates with the phone’s features, and supports push messages and GPS location.&lt;/p&gt;

&lt;p&gt;In addition to the accessibility team teaching accessibility to our colleagues, FINN.no designers provide a design framework where developers can find elements used at FINN.no. These elements are all checked for contrast and other accessibility issues.&lt;/p&gt;

&lt;figure&gt;
  &lt;img src=&quot;/images/2017-10-30-finn-wins-universal-design-award/before-after-app.jpg&quot; alt=&quot;FINN user page before and after user test with people with disabilities&quot; /&gt;
  &lt;figcaption class=&quot;b1&quot;&gt;A user test regarding people with disabilities made it obvious that there was a confusion between My messages, My ads, My searches and My findings. The test made it clear that this confusion was a problem for everyone, focused our efforts on solving the problem.&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;h2&gt;Increases Competitiveness&lt;/h2&gt;
&lt;p&gt;The Innovation Award for Universal Design was awarded for the third time. The category winners also competed for a main prize. FINN.no competed for this award against the online retailer Kolonial.no, the children’s newspaper Aftenposten Junior, a changing room facility at Solvik camping, Hamaren Activity Park, the Vision of the Fjords sightseeing boat, and the soundproofing felt FeltRoll. The Vision of the Fjords won the main prize.&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Universal design allows everyone to participate on equal terms. The seven categories are good examples of how universal design triggers innovation, boosting competitiveness and providing a more open and better society, says Program Director Onny Eikhaug in &lt;a href=&quot;https://doga.no/&quot; target=&quot;_blank&quot;&gt;Design and Architecture Norway (DOGA)&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;Far Ahead of the Competition&lt;/h2&gt;
&lt;p&gt;In the jury celebration, FINN.no was praised for giving users a simple, safe, and engaging way to buy and sell items:&lt;/p&gt;

&lt;p&gt;“FINN.no is innovative by being ahead in using new technology, and over the years has developed an entire ecosystem of services that constantly improve and develop further. Both the web solution and the FINN.no app rank high on ease of use and universal design, and are far ahead of many other digital products”, &lt;a href=&quot;https://doga.no/dogas-priser/innovasjonsprisen/vinnere-2017/interaksjonsdesign-og-digitale-losninger/&quot; target=&quot;_blank&quot;&gt;the jury writes&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;(based on and translated from &lt;a href=&quot;http://pressenytt.no/nor/Artikler/Design-og-arkitektur/Markedsplassen-som-aapner-doeren-til-samfunnet&quot;&gt;Pressenytt article in Norwegian on FINN wins Universal Design Award&lt;/a&gt;) by Henning Poulsen&lt;/p&gt;

&lt;figure&gt;
  &lt;img src=&quot;/images/2017-10-30-finn-wins-universal-design-award/finn-tweet-doga-award.jpg&quot; alt=&quot;FINN tweet with pictures from the award seremony&quot; /&gt;
  &lt;figcaption class=&quot;b1&quot;&gt;FINN tweet after winning the category interaction design and digital solutions&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;h3&gt;Other blog posts on accessibility in FINN.no:&lt;/h3&gt;
&lt;p&gt;&lt;a href=&quot;/2014/11/19/workshop-with-blindfolded-lunch/&quot; target=&quot;_blank&quot;&gt;Workshop with Blindfolded Lunch&lt;/a&gt;&lt;br /&gt;
&lt;a href=&quot;/2016/06/13/how-to-make-your-colleagues-think-accessibility/&quot; target=&quot;_blank&quot;&gt;How to make your Colleagues think Accessibility&lt;/a&gt;&lt;br /&gt;
&lt;a href=&quot;/2017/06/07/wai-aria-and-its-true-impact-on-assistive-technologies/&quot; target=&quot;_blank&quot;&gt;WAI-ARIA and its true impact on assistive technologies&lt;/a&gt;&lt;br /&gt;&lt;/p&gt;

</content>
        
        <author>
            <name>Lotte Johansen</name>
            
            <email></email>
            
        </author>
        
    </entry>
    
    <entry>
        <title>Akka workshop at Finn.no</title>
        <link rel="alternate" type="text/html" href="https://tech.finn.no2017/10/17/akka-workshop-finn-no/"/>
        <updated>2017-10-17T06:56:49+00:00</updated>
        <id>https://tech.finn.no/2017/10/17/akka-workshop-finn-no</id>
        <content type="html">&lt;figure&gt;
   &lt;img class=&quot;center-block&quot; src=&quot;/images/2017-10-17-akka-workshop-finn-no/DSC_0083.JPG&quot; alt=&quot;alt&quot; title=&quot;Akka workshoppers hard at work!&quot; /&gt;
   &lt;figcaption style=&quot;text-align:center; font-style:italic;&quot;&gt; Akka workshoppers hard at work!&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;In FINN.no we have experience in running distributed systems, and we often rely on asynchronous communication between our applications. &lt;a href=&quot;http://tech.finn.no/2015/09/22/the-process-of-using-kafka/&quot;&gt;Apache Kafka&lt;/a&gt; is the work-horse on the house, and it’s doing an excellent job for us.&lt;/p&gt;

&lt;p&gt;But this doesn’t mean that we shouldn’t learn new things - or look at other solutions.&lt;/p&gt;

&lt;p&gt;Akka is an implementation of &lt;a href=&quot;https://en.wikipedia.org/wiki/Actor_model&quot;&gt;the Actor Model&lt;/a&gt; - a concept of concurrent processing first published in 1973. The Actor Model is all about two ideas: Actors and Messages.&lt;/p&gt;

&lt;figure&gt;
   &lt;img class=&quot;center-block&quot; src=&quot;/images/2017-10-17-akka-workshop-finn-no/actor-model.png&quot; alt=&quot;alt&quot; title=&quot;Actors sending messages&quot; /&gt;
   &lt;figcaption style=&quot;text-align:center; font-style:italic;&quot;&gt; Actors sending messages&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;A number of Actors communicate to each other using messages. An Actor will process its received messages sequentially, and send messages to other actors asynchronously. It can also keep state, perform side-effects and supervise other actors. This small hand-full of operations makes the Actor Model easy to reason about and within one Actor we need not worry about the complexity of concurrency - one message is processed after another.&lt;/p&gt;

&lt;p&gt;The power of the Actor Model comes from combining (often specialized) Actors in a supervised hierarchy. Concurrency and scalability comes naturally as the sending of messages between them are asynchronous. Reliability and uptime comes from a let-it-crash philosophy and supervision done through special Supervisor-Actors.&lt;/p&gt;

&lt;figure&gt;
   &lt;img class=&quot;center-block&quot; src=&quot;/images/2017-10-17-akka-workshop-finn-no/DSC_0085.png&quot; alt=&quot;alt&quot; title=&quot;Arild explaining the async monad!&quot; /&gt;
   &lt;figcaption style=&quot;text-align:center; font-style:italic;&quot;&gt; Arild wrapping heads around the async monad!&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;The workshop was a hands-on with a few slides between the exercises. You can &lt;a href=&quot;https://github.com/mariatsji/akka-workshop&quot;&gt;do the tasks yourself by cloning&lt;/a&gt; and checking out the master-branch (for java) or the kotlin-branch.&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://mariatsji.github.io/akka-workshop/&quot;&gt;Slides&lt;/a&gt;
&lt;a href=&quot;https://akka.io&quot;&gt;Resources&lt;/a&gt;&lt;/p&gt;

</content>
        
        <author>
            <name>Arild Nilsen</name>
            
            <email></email>
            
        </author>
        
        <author>
            <name>Sjur Millidahl</name>
            
            <email></email>
            
        </author>
        
    </entry>
    
    <entry>
        <title>Deep NLP-based Recommenders at Finn.no</title>
        <link rel="alternate" type="text/html" href="https://tech.finn.no2017/09/08/NLP-based-recommenders-at-finn/"/>
        <updated>2017-09-08T13:56:49+00:00</updated>
        <id>https://tech.finn.no/2017/09/08/NLP-based-recommenders-at-finn</id>
        <content type="html">&lt;p&gt;During a hackathon at FINN.no, we figured we wanted to learn more about deep NLP-models. FINN.no has a large database with ads of people trying to sell stuff (around 1 million active ads at any time), and they are categorized into a category tree with three or four layers. For example, full suspension bikes can be found under “Sport and outdoor activities” / “Bike sport” / “Full suspension bikes”.&lt;/p&gt;

&lt;p&gt;In our daily jobs we are working on recommendations. There, we already have a content based (tf-idf) recommender build on Solr’s More Like This. It seems to work well in areas where our collaborative filtering approaches does not. Would it be possible to build a deep learning NLP-model of similar performance?&lt;/p&gt;

&lt;p&gt;To achieve a measure of similarity, building a classifier of the previously mentioned categories seemed like a good choice, since we already had a lot of pre-existing data. The NLP team at Schibsted had already tokenized around six million ads as well as trained a word2vec model for us - we were ready to roll!&lt;/p&gt;

&lt;p&gt;Some preprocessing still had to be done. We ran through all ads, concatenated the title and description strings, and after a quick look at the data took the first 15 words of each ad.&lt;/p&gt;

&lt;figure&gt;
   &lt;img class=&quot;center-block&quot; src=&quot;/images/2017-09-08-NLP-based-recommenders-at-finn/model architecture proposed by the paper.png&quot; alt=&quot;alt&quot; title=&quot;Model architecture proposed by the paper&quot; /&gt;
   &lt;figcaption style=&quot;text-align:center; font-style:italic;&quot;&gt; Model architecture proposed by the paper&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;Our initial experiments were done with a simple &lt;a href=&quot;https://github.com/fchollet/keras/blob/2.0.3/examples/reuters_mlp.py&quot;&gt;“Bag of words” model included in the Keras repository&lt;/a&gt;, but we promptly switched over to &lt;a href=&quot;https://arxiv.org/pdf/1408.5882.pdf&quot;&gt;“Convolutional Neural Networks for Sentence Classification” based architecture&lt;/a&gt; after hearing about it from our colleague, Tobias. By looking at the first 15 words of the ad, and using 200 dimensional embeddings for each word, our input is transformed into a 15x200 matrix. We apply three different convolutions on each document. The three convolutions looks at 2, 3 and 4 words (kernel sizes) in each convolution. It then max-pools each over the whole document, so that you end up with one value per document per convolution. For each kernel size you do 100 different filters. Finally you add a dense layer for classification. In addition to the standard model described in the paper, we experimented with different kernel sizes, number of filters, dense layers, batch normalization and dropout. We also added several losses, so that the model optimized both the higher and lower category at the same time. That helped.&lt;/p&gt;

&lt;figure&gt;
   &lt;img class=&quot;center-block&quot; src=&quot;/images/2017-09-08-NLP-based-recommenders-at-finn/keras representation of our nlp model.png&quot; alt=&quot;alt&quot; title=&quot;Keras representation of our NLP model&quot; /&gt;
   &lt;figcaption style=&quot;text-align:center; font-style:italic;&quot;&gt; Keras representation of our NLP model&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;So how did it go? Our hackathon model managed to categorize 10’000 ads into 359 categories with an accuracy of 50%. We were surprised it worked so well, it was about the same accuracy image models have achieved on roughly the same ads. After tuning and pruning it further and adding 1 million data points, we have reached an accuracy of 70% on the 359 classes. In comparison, the bag-of-words model we started with managed an accuracy rate of around 25% and image recognition models have reached accuracies around 50%.&lt;/p&gt;

&lt;h2 id=&quot;using-the-model-in-recommendations&quot;&gt;Using the model in recommendations&lt;/h2&gt;

&lt;p&gt;It is usually the case that category-similarity translates decently to ad-similarity. Using our classifier model we can serve users more ads similar to what they’re already seeing, based on the text of a selected ad.&lt;/p&gt;

&lt;p&gt;We use the model by cutting the last dense layer (called feature layer in figure above), then comparing normalized dot products (cosine similarity) between objects. Since our our benchmarks for judging anything a success or failure is based on how it performs in a production environment, we went ahead and did that. 
This gave us decent results using only text, performing about 5-6% percent worse than our top collaborative filtering approach. When we made an ensemble model combining text and collaborative filtering we managed to improve our existing best model by about 10%.
This is likely due to better supporting “cold ads”, or ads without traffic, while still retaining the accuracy of the collaborative filtering-model.&lt;/p&gt;

&lt;figure&gt;
   &lt;img class=&quot;center-block&quot; src=&quot;/images/2017-09-08-NLP-based-recommenders-at-finn/cold ad.png&quot; alt=&quot;alt&quot; title=&quot;Cold ad&quot; /&gt;
   &lt;figcaption style=&quot;text-align:center; font-style:italic;&quot;&gt; An example of a “cold ad”, where we think our NLP model does a better job at finding relevancy than the traditional collaborative filtering approach&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;figure&gt;
   &lt;img class=&quot;center-block&quot; src=&quot;/images/2017-09-08-NLP-based-recommenders-at-finn/collaborative filtering.png&quot; alt=&quot;alt&quot; title=&quot;Collaborative filtering&quot; /&gt;
   &lt;figcaption style=&quot;text-align:center; font-style:italic;&quot;&gt; Collaborative filtering recommendations&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;figure&gt;
   &lt;img class=&quot;center-block&quot; src=&quot;/images/2017-09-08-NLP-based-recommenders-at-finn/nlp recommendations.png&quot; alt=&quot;alt&quot; title=&quot;NLP Recommendations&quot; /&gt;
   &lt;figcaption style=&quot;text-align:center; font-style:italic;&quot;&gt; NLP Recommendations&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;h2 id=&quot;further-work&quot;&gt;Further work&lt;/h2&gt;

&lt;p&gt;The pure text-model does not prioritize the popularity (or perhaps by proxy, how good the ad is) of the ad at all. This leads us to suspect that although users are being directed to similar ads, they could for example be missing an enticing image to make engagement likely. Seeing how the ensemble model in the end is optimized for click-rate, it likely only gives the NLP model high priority when the ad has low traffic. It would be interesting to somehow introduce this aspect into the NLP model.&lt;/p&gt;

&lt;p&gt;We would like to eventually have a more thorough NLP representation of all our ads for other teams to build services and functionality on, and this recommender is an important first step to achieve that.&lt;/p&gt;

&lt;p&gt;The code can be found at &lt;a href=&quot;https://github.com/finn-no/keras-conv-sentence-classifier&quot;&gt;FINN.no’s github page&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://arxiv.org/pdf/1408.5882.pdf&quot;&gt;Resources&lt;/a&gt;&lt;/p&gt;

</content>
        
        <author>
            <name>Joakim Rishaug</name>
            
            <email></email>
            
        </author>
        
        <author>
            <name>Simen Eide</name>
            
        </author>
        
    </entry>
    
    <entry>
        <title>The summer interns of 2017</title>
        <link rel="alternate" type="text/html" href="https://tech.finn.no2017/08/08/summerinterns-2017/"/>
        <updated>2017-08-08T10:56:49+00:00</updated>
        <id>https://tech.finn.no/2017/08/08/summerinterns-2017</id>
        <content type="html">&lt;h1 id=&quot;the-starting-point&quot;&gt;The starting point&lt;/h1&gt;
&lt;p&gt;In a galaxy very very close by, there were 5 brave summer interns working for FINN.no. They came from the far north (NTNU) and the far south (UiO) of the cold and rainy plains of Norway. 4 of them were taught in the arts of the computer, and one in the teachings of the design. They had a mission: to improve the current user experience for advertisers on FINN Torget, and to decrease the amount of zero-contact ads. A tough mission, but our heroes have valiantly fought during the summer of 2017 to resolve this quest.&lt;/p&gt;

&lt;p&gt;During the first week of the summer internship we (the interns) were introduced to FINN.no, its systems and our coaches for the summer project; Martin, the brilliant programmer and Ingrid, the amazing designer. In order to ensure everyone on the team were on the same page a brainstorming session was held. During this session possible solutions for the summer project were developed, and our mutual ambition for this project was put into words. Our ambition has been to: “Increase the amount of ads receiving feedback/being sold on FINN.no” and “Help advertisers on FINN.no make better ads”.&lt;/p&gt;

&lt;p&gt;In the past 2 months two concepts have been realized, with quite surprising results and developments. But you have to read the rest of the document to know exactly what surprising developments took place :) Sorry, that’s just life bro/hon!&lt;/p&gt;

&lt;h1 id=&quot;project-1-tilbakemelding-feedback&quot;&gt;Project 1: Tilbakemelding (Feedback)&lt;/h1&gt;
&lt;p&gt;Have you ever created an ad and received no feedback, no likes and no messages? This is the reality of approximately 50 % of all ads published on FINN Torget in Norway. Imagine if there was a way for advertisers to know exactly how they could improve their ads, what would it look like?
“Tilbakemelding” is one possibility. Tilbakemelding is a button located at the front page of an ad for all potential buyers to see and use. If a user does not like an ad, and feels that sending a message is too much of a hassle, they can push this button. By doing so, a set of possible improvements to the ad will be revealed, and can easily be selected. Compared to sending a message this is less time consuming and the feedback can be used to create statistics to help the advertiser improve their ads. In this project, the focus was first and foremost to create the button and observe how users of FINN.no would experience the new functionality.&lt;/p&gt;

&lt;p&gt;After implementing the solution and launching it on FINN.no website, it was determined the new functionality was used by barely any users. Some tears were shed, and a new direction was chosen. It was decided to rename the button, and make it into a notification button. By using the new button users can choose specific tags and be notified when this tag has been edited in the ad. It turns out this option was used more, however not enough to deem this a valuable asset to FINN.no’s website.&lt;/p&gt;

&lt;figure&gt;
   &lt;img class=&quot;center-block&quot; src=&quot;/images/2017-08-08-Summerinterns-2017/Tilbakemelding1.png&quot; alt=&quot;alt&quot; title=&quot;feedback1&quot; /&gt;
   &lt;figcaption style=&quot;text-align:center; font-style:italic;&quot;&gt;Tilbakemelding (feedback) accordion&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;h2 id=&quot;technical-solution&quot;&gt;Technical solution&lt;/h2&gt;
&lt;p&gt;There were several steps to go through before we could launch our solution. We were lucky to get the opportunity to develop it the whole way from backend to frontend. As we have understood, this is a common practice in FINN.no. This so that the developer can feel a greater ownership to his or her code, and can follow it the whole way through the process. Our solution took the form of a separate API running inside kubernetes in FINN.no’s tested and proven microservice architecture. PostgreSQL was chosen as the underlying storage, due to both simplicity and familiarity. Docker and Docker Compose were utilized to ensure that dependencies and environments were identical throughout development, staging, production, and to increase the overall speed of development.&lt;/p&gt;

&lt;p&gt;Each tag that’s chosen by a user was stored in the database connected to different types of data like the Id’s of the seller, ad and the person clicking on the tags. We used Java, Spring Boot and IntelliJ IDEA to make our API. To represent our solution we used JavaScript, HTML and CSS. Thankfully FINN.no has its own implementation guide that helps both designers and developers to get an understanding of how different solutions should look like, and much of it has already been implemented so it saves developers a lot of time that they can use on other things more fun than CSS.&lt;/p&gt;

&lt;figure&gt;
   &lt;img class=&quot;center-block&quot; src=&quot;/images/2017-08-08-Summerinterns-2017/pro1.png&quot; alt=&quot;alt&quot; title=&quot;feedback1&quot; /&gt;
   &lt;figcaption style=&quot;text-align:center; font-style:italic;&quot;&gt;&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;h2 id=&quot;results-ux-design&quot;&gt;Results UX Design&lt;/h2&gt;
&lt;p&gt;Several prototypes were designed using Sketch and Invision, with people inhouse and on the streets helping us evaluate the design. The tests helped us rename several of the tags, helped us deciding the looks of the button and the tags, and the means by which a user should access them. In addition, more elaborate user tests in the prototyping lab of FINN.no were successfully completed, and valuable insights were obtained. Although the user surveys proved a majority of FINN.no users, both buyers and advertisers, wanted a system to improve ads, the user tests proved contrary. It was revealed buyers were not interested in helping improve ads. If they wanted information about the product, they would message the advertiser directly, thus limiting the competition for the ad and receiving a response quickly. Most users did not notice the button at first glance, partly due to the location, but also because they did not feel a need to use it.&lt;/p&gt;

&lt;figure&gt;
   &lt;img class=&quot;center-block&quot; src=&quot;/images/2017-08-08-Summerinterns-2017/Tilbakemelding2.png&quot; alt=&quot;alt&quot; title=&quot;feedback1&quot; /&gt;
   &lt;figcaption style=&quot;text-align:center; font-style:italic;&quot;&gt;Activated drop-down menu for Tilbakemelding (feedback), displaying tags with different ways to improve the ad. &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;h2 id=&quot;recommendations&quot;&gt;Recommendations&lt;/h2&gt;
&lt;p&gt;During the first weeks of the project, several surveys regarding aspects of “Tilbakemelding” were given to a set of FINN users. Based on these surveys, we know advertisers want advice in how to improve their ads and buyers on FINN.no are not interested in ads if they are too poorly made. Making users help advertisers by giving feedback has proven to be difficult though. Another way of giving advice to advertisers would be to make an automated system capable of doing so.
Using an automated system rather than user feedback would also minimize the possibility of buyers misusing or abusing the feedback module (giving inaccurate/wrong feedback). This system could analyse the amount of pictures, the quality of the pictures, the amount of text, the price etc. in terms of similar ads and compare them to the ad in question. The tags used in “Tilbakemelding” could be used as a starting point, with the most focus on number of pictures and picture quality. According to our results, users would be first and foremost interested in the pictures of the ad. Another recommendation based on our experience is to start user testing early on. By doing so, weaknesses in the prototype can be found before a lot of time is spent programming.&lt;/p&gt;

&lt;h1 id=&quot;project-2-redesign-ad-dashboard&quot;&gt;Project 2: Redesign ad dashboard&lt;/h1&gt;
&lt;p&gt;After finishing “Tilbakemelding”, it was decided to do a redesign of the ad dashboard page in order to improve advertisers’ user experience. As can be seen on the picture, several of the buttons have been rearranged, and the statistics have been given more space and importance. The old message system has been upgraded to improve the flow and aesthetics. Tips into how your ad can be improved have been added to motivate and help advertisers make even better ads. Lastly, a timeline showing the past, present and future status of the ad are displayed.&lt;/p&gt;

&lt;h2 id=&quot;technical-solution-1&quot;&gt;Technical solution&lt;/h2&gt;
&lt;p&gt;The redesigned ad dashboard page has been created using Node.js and React. By splitting FINN.no into multiple smaller microservices it allows developers to create/modify pages, while still keeping development environments rich and easy to use. We created a Node.js server for handling server rendering and proxying requests to other backend services and a React web application for the frontend. We still utilize existing backend services at FINN, e.g for getting the correct buttons for an ad in a specific state. This allows our newly developed front end to be switched out seamlessly without the user knowing the switched from one application to another. FINN.no has quite a lot of backend services that we can take advantage of to speed up the process. One example of this is the use of Finnlets which creates the identical footer and header while the server is rendering the page to the user.&lt;/p&gt;

&lt;figure&gt;
   &lt;img width=&quot;40%&quot; height=&quot;40%&quot; class=&quot;center-block&quot; src=&quot;/images/2017-08-08-Summerinterns-2017/pro2.2.png&quot; alt=&quot;alt&quot; title=&quot;feedback1&quot; /&gt;
   &lt;figcaption style=&quot;text-align:center; font-style:italic;&quot;&gt;&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;h2 id=&quot;results-ux-design-1&quot;&gt;Results UX design&lt;/h2&gt;
&lt;p&gt;New prototypes in Sketch and Invision were made. According the user tests in FINN.no’s lab, most people understood the new system, and felt it was fitting nicely into FINN.no current website. Several users did not notice the changes. Almost all users understood the ad timeline, the statistics graph, the new message system and how they could use the given information to improve their ad. One user mentioned he felt compelled to change his ad after inspecting the timeline, and noticing his ad would soon expire. In most cases it took some time before the advice for improving the ad was discovered. However, all users said they wanted to follow the advices after spotting them. In the user test, the most remarks concerned existing buttons and text on the current webpage. These remarks were mostly concerned with the “Skjul annonse i søkeresultater” (Hide ad in search results) button and the “Vanlig trafikk” (regular traffic) statistics, which were both found to be confusing. A majority of the users did not want to purchase “Synlighet” (visibility) because it felt like a waste of money, and told us they had never used this function.&lt;/p&gt;

&lt;figure&gt;
   &lt;img class=&quot;center-block&quot; src=&quot;/images/2017-08-08-Summerinterns-2017/Innsikt1.png&quot; alt=&quot;alt&quot; title=&quot;feedback1&quot; /&gt;
   &lt;figcaption style=&quot;text-align:center; font-style:italic;&quot;&gt; The statistics module in the redesigned ad dashboard page&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;figure&gt;
   &lt;img class=&quot;center-block&quot; src=&quot;/images/2017-08-08-Summerinterns-2017/Innsikt2.png&quot; alt=&quot;alt&quot; title=&quot;feedback1&quot; /&gt;
   &lt;figcaption style=&quot;text-align:center; font-style:italic;&quot;&gt;The message module in the redesigned ad dashboard page&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;h2 id=&quot;recommendations-1&quot;&gt;Recommendations&lt;/h2&gt;
&lt;p&gt;As mentioned, the advice for improving an ad was not noticed by most users, even though they were perceived as helpful and motivating when spotted. By making the advices more noticeable (bigger, more visible colors, more central location) when an ad is either about to expire or has a very low number of views, the people who are in the most need of this information would be more easily exposed to this information. According to our user test results, people who noticed the advices would most likely follow them. Lastly we would recommend  “Vanlig trafikk”, “Kjøp synlighet” and “Skjul annonse i søkeresultater” to be renamed. “Vanlig trafikk” could be called “Totalt antall visninger” and “Kjøp synlighet” could be called “Øk synlighet”. A new user test to establish new names should be performed, as well as an user test into making purchasing “Synlighet” more attractive and understandable.&lt;/p&gt;

&lt;h1 id=&quot;final-thoughts&quot;&gt;Final thoughts&lt;/h1&gt;
&lt;p&gt;Working with FINN.no this summer has been very educational and challenging! Although there has been a lot of work, FINN.no made sure we always felt at home in their quarters (it’s actually an old prison for females, who would have known?). We have met and learned from a wide range of professionals this summer, used high-quality software to solve our problems and learned new methods for dealing with challenges. In addition, it turns out working at FINN.no is also amazingly fun! We have been treated to ice cream, pizza and other delicious food (some of us probably have to hit the gym soon), and lots of social activities such as “Fangene på fortet” escape rooms and FINN.no summer party. We, the summer interns, feel very fortunate to have been the chosen ones this year, and we know next year’s summer interns will feel the same!&lt;/p&gt;

&lt;figure&gt;
   &lt;img class=&quot;center-block&quot; src=&quot;/images/2017-08-08-Summerinterns-2017/IMG_20170719_190335.jpg&quot; alt=&quot;alt&quot; title=&quot;The interns&quot; /&gt;
   &lt;figcaption style=&quot;text-align:center; font-style:italic;&quot;&gt;The interns and our fantastic coaches&lt;/figcaption&gt;
&lt;/figure&gt;

</content>
        
        <author>
            <name>Inga N. Søreide</name>
            
        </author>
        
        <author>
            <name>Bjørn Hoxmark</name>
            
            <email></email>
            
        </author>
        
        <author>
            <name>Henriette Ekeberg</name>
            
        </author>
        
        <author>
            <name>Kristoffer K. Larsen</name>
            
            <email></email>
            
        </author>
        
        <author>
            <name>Alfred Birketvedt</name>
            
        </author>
        
    </entry>
    
    <entry>
        <title>From Hadoop and Cassandra to Kafka Streams</title>
        <link rel="alternate" type="text/html" href="https://tech.finn.no2017/07/31/from-hadoop-and-cassandra-to-kafka-streams/"/>
        <updated>2017-07-31T12:00:00+00:00</updated>
        <id>https://tech.finn.no/2017/07/31/from-hadoop-and-cassandra-to-kafka-streams</id>
        <content type="html">&lt;h2 id=&quot;some-context&quot;&gt;Some context&lt;/h2&gt;

&lt;p&gt;People who publish their classified ads on &lt;a href=&quot;https://www.finn.no&quot;&gt;FINN.no&lt;/a&gt; get access to various statistics to see how their ads are performing. For a user it can look something like this:&lt;/p&gt;

&lt;figure&gt;
    &lt;img class=&quot;center-block&quot; src=&quot;/images/2017-07-31-from-hadoop-and-cassandra-to-kafka-streams/statistics-on-page.png&quot; alt=&quot;alt&quot; title=&quot;Statistics about an ad&quot; /&gt;
    &lt;figcaption style=&quot;text-align:center; font-style:italic;&quot;&gt;Statistics the owner of a realestate ad gets to see&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;The top left bar chart shows the repartition of the incoming traffic by day and the legend to the right of it shows the total numbers for each type of incoming traffic.
The lower section is divided in 3 parts:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;the left part shows the number of views by unique users;&lt;/li&gt;
  &lt;li&gt;the one to the center shows how many users have been notified of the ad by email and how many added the ad to their favorites;&lt;/li&gt;
  &lt;li&gt;the right part shows how many viewers out of the total come from a specific type of traffic.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This gives the user basic insight into the reach of their ad, such as how many views it has and how many unique users have viewed it.&lt;/p&gt;

&lt;p&gt;Around November 2016 there was a request to show more detailed information about the viewers, such as the age, gender and location distribution of the viewers (demographic information) so the owner could potentially change an ad to better fit the audience they wanted.&lt;/p&gt;

&lt;h2 id=&quot;existing-solution&quot;&gt;Existing solution&lt;/h2&gt;

&lt;p&gt;As users view ads, do actions (such as send a message to an ad’s owner or scroll down and read the whole page, for example), these actions are gathered and published internally on &lt;a href=&quot;https://kafka.apache.org/&quot;&gt;Apache Kafka&lt;/a&gt;. These streams of data then become the basis for computing the statistics above. The plan was then to also publish demographic data about the viewers (such as location, age and gender) on Kafka and join the user actions with this demographic data to provide enhanced statistics.&lt;/p&gt;

&lt;p&gt;At the time, the action events published on Kafka were saved to Cassandra &lt;a href=&quot;http://cassandra.apache.org/&quot;&gt;Apache Cassandra&lt;/a&gt; clusters, and statistics were being computed as batches on an aging &lt;a href=&quot;http://hadoop.apache.org/&quot;&gt;Apache Hadoop&lt;/a&gt; cluster reading from Cassandra. Both our Hadoop and Cassandra clusters had not receive much love recently and were all on end-of-life versions.  The old system also had an increasing tendency to fail, so we were also in the process to decide to either spend our time to refresh/renew the old system or replace it.&lt;/p&gt;

&lt;figure&gt;
    &lt;img class=&quot;center-block&quot; src=&quot;/images/2017-07-31-from-hadoop-and-cassandra-to-kafka-streams/previous-archictecture.png&quot; alt=&quot;alt&quot; title=&quot;Previous architecture using Cassandra and Hadoop&quot; /&gt;
    &lt;figcaption style=&quot;text-align:center; font-style:italic;&quot;&gt;A simplified overview of the previous architecture&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;h2 id=&quot;replacement-solution&quot;&gt;Replacement solution&lt;/h2&gt;

&lt;p&gt;We first tried to find a good way of joinining the latest demographic data being received through Kafka as part of the existing statistics computation done with Hadoop. We quickly learned that efficient communication with Kafka from a Hadoop job was not easy to get right. After some experimentation we had a solution that sort of worked, but it was complicated and far from elegant.&lt;/p&gt;

&lt;p&gt;After doing some research we started to look at the possibility of using &lt;a href=&quot;https://kafka.apache.org/documentation/streams/&quot;&gt;Kafka Streams&lt;/a&gt;. Reading the introductory &lt;a href=&quot;https://www.confluent.io/blog/introducing-kafka-streams-stream-processing-made-simple/&quot;&gt;blog post&lt;/a&gt; - especially the “Simple is Beautiful” paragraph - it felt like Streams was just what we needed.&lt;/p&gt;

&lt;p&gt;The most interesting aspects were:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;no more external runtime for jobs (in our case YARN/Hadoop);&lt;/li&gt;
  &lt;li&gt;no more intermediary external storage (Cassandra);&lt;/li&gt;
  &lt;li&gt;built-in fault tolerance;&lt;/li&gt;
  &lt;li&gt;only POJOs running as normal Java apps;&lt;/li&gt;
  &lt;li&gt;limiting the number of components by using Kafka for the whole solution.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This would lead to less moving parts, less maintenance and what we saw as a simpler and more elegant solution.&lt;/p&gt;

&lt;p&gt;We also evaluated other solutions, amongst which:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;upgrade our Hadoop and Cassandra clusters;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://spark.apache.org/&quot;&gt;Apache Spark&lt;/a&gt; micro-batches on top of an &lt;a href=&quot;https://parquet.apache.org/&quot;&gt;Apache Parquet&lt;/a&gt; storage filled from Kafka;&lt;/li&gt;
  &lt;li&gt;hybrid solutions with hyperloglog and other similar cardinality tricks to estimate the unique users visiting an ad.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We were already using Kafka (and it is seen as a central part of our future architecture), so Streams was deemed to be the most attractive and straight-forward solution.&lt;/p&gt;

&lt;h2 id=&quot;streams-in-use&quot;&gt;Streams in use&lt;/h2&gt;

&lt;p&gt;The first prototype took less than a week to get up and running, and from there it took 6 months to develop a fully operational, properly sized version running in production on our &lt;a href=&quot;https://kubernetes.io/&quot;&gt;Kubernetes&lt;/a&gt; container cluster.&lt;/p&gt;

&lt;p&gt;As part of development we also wrote tools for monitoring, error handling/diagnostics, reprocessing and for migration of the existing statistics out of the old Cassandra storage and into our new storage. During this development, the original plan to join in demographics data was put on hold (for non-technical reasons) and the project turned into a more technical one: move the statistics system out of the aging Hadoop and Cassandra clusters and migrate to something that incurs less maintenance and fits better with our overall technology strategy.&lt;/p&gt;

&lt;h3 id=&quot;requirements&quot;&gt;Requirements&lt;/h3&gt;

&lt;p&gt;The amount of data is is reasonably large, about 100 million messages a day, and the main requirements were:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;keep the near-realtime computing of the statistics we had in the previous solution;&lt;/li&gt;
  &lt;li&gt;be able to reprocess data up to 3 months back in time (in case of failure or bugs);&lt;/li&gt;
  &lt;li&gt;be able to integrate late arrivals to some extent.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;architecture&quot;&gt;Architecture&lt;/h3&gt;

&lt;p&gt;We went through multiple versions and the components evolved both in size and responsibility during development, and below is how it looks now.&lt;/p&gt;

&lt;figure&gt;
    &lt;img class=&quot;center-block&quot; src=&quot;/images/2017-07-31-from-hadoop-and-cassandra-to-kafka-streams/last-architecture.png&quot; alt=&quot;alt&quot; title=&quot;Last architecture&quot; /&gt;
    &lt;figcaption style=&quot;text-align:center; font-style:italic;&quot;&gt;A simplified overview of the replacement architecture&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;The &lt;strong&gt;replicator&lt;/strong&gt; copies a subset of short lived topics (3 days retention) into our own long lived topics (90 days retention) to support reprocessing of the last 3 months. It is a simple read-write (Kafka consumer and producer) that computes the hash of the message as the key for deduplication downstream.&lt;/p&gt;

&lt;p&gt;The &lt;strong&gt;aggregator&lt;/strong&gt; is the interesting bit that actually computes the base data for our statistics: the counts. A simplified versions of the Streams code to achieve this would look something like this:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;// grouping by unique key which consists
// of the id of the ad, the day the action
// happened and the type of the action
// and then counting

kstream.groupBy((key, action) -&amp;gt;   // build the new key by which we want to count
            new Key(action.ad,     // ex: ad number 12345678
                    action.day,    // ex: day 17-02-2017
                    action.type))  // ex: &quot;click&quot;
       .count(&quot;count-store&quot;)       // counts the number of messages for each key
       .to(&quot;count-topic&quot;);         // outputs each update of the count the Kafka topic
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The production code does more than that with &lt;a href=&quot;https://avro.apache.org/&quot;&gt;Apache Avro&lt;/a&gt; serialization / deserialization, filtering of bad data, deduplicating, reporting metrics etc., but the above code shows the main logic the component consists of.&lt;/p&gt;

&lt;p&gt;Finally, the &lt;strong&gt;sink&lt;/strong&gt; is just an instance of Kafka Connect configured to use the JDBC sink - with no custom code. It reads from the output topic from the &lt;strong&gt;aggregator&lt;/strong&gt; and writes into a &lt;a href=&quot;https://www.postgresql.org/&quot;&gt;PostgreSQL&lt;/a&gt; database.&lt;/p&gt;

&lt;p&gt;All of these components are webapps running in Kubernetes and can be scaled up or down easily. And while the replicator and the aggregator are &lt;a href=&quot;https://projects.spring.io/spring-boot/&quot;&gt;Spring Boot&lt;/a&gt; based webapps, the sink is just an off-the-shelf Kafka-REST webapp.&lt;/p&gt;

&lt;h3 id=&quot;data-quality&quot;&gt;Data quality&lt;/h3&gt;

&lt;h4 id=&quot;avro-and-schema-registry&quot;&gt;Avro and Schema Registry&lt;/h4&gt;

&lt;p&gt;We use Avro and the Schema Registry to enforce schemas on the Kafka messages so that changes to the message structure maintain compatibility over time. This proves to be quite robust any incompatible change to the schema gets immediately rejected.&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;// build the serialization/deserialization object

GenericAvroSerde avroSerde = 
    new GenericAvroSerde(registryClient, registryProps);

// build the KStream with Avro

new KStreamBuilder()
    .stream(Serdes.String(), avroSerde, topics);
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;em&gt;A note about developing with Avro and the schema registry:&lt;/em&gt;
While in the early stages of development, using the schema registry proved cumbersome: breaking changes happens quite often during development and maintaining compatibility or overriding it takes time to get used to. This was however very good training for applying actual real changes to the schema that will inevitably be required later on when running in production. Special care is required with the internal Streams topics that also contain Avro messages.&lt;/p&gt;

&lt;h4 id=&quot;streams-filtering&quot;&gt;Streams filtering&lt;/h4&gt;

&lt;p&gt;We filter out bad messages (missing or corrupted pieces of information) with the filtering method of the Streams and plain old Java.&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;// filter out actions for the owner of the ad
// and actions that are missing some data

kstream.filter((key, action) -&amp;gt; 
                   action.isNotFromOwner() 
                   &amp;amp;&amp;amp; action.hasRequiredData());
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h4 id=&quot;dealing-with-duplicates&quot;&gt;Dealing with duplicates&lt;/h4&gt;

&lt;p&gt;There is no built-in support in Streams to deal with duplicates, so non-idempotent processing of the messages then becomes an issue. The strategy we adopted is to calculate a hash of each message value and set it as its key so that consumers downstream can deduplicate based on an in-memory LRU cache of these hashes (because all the identical keys are guaranteed to be in the same partition, and thus end up in the same consumer on the same thread).&lt;/p&gt;

&lt;p&gt;&lt;em&gt;In the replicator:&lt;/em&gt;&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;// compute a hash of the message and
// set it as the key

String key = DigestUtils.sha1Hex(message);
producer.send(new ProducerRecord&amp;lt;&amp;gt;(topic, key, message);
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;em&gt;In the aggregator:&lt;/em&gt;&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;// cache processed key for 10 minutes

KEYS = CacheBuilder.newBuilder()
                   .maximumSize(100000L)
                   .expireAfterWrite(10L, TimeUnit.MINUTES)
                   .build();

// keep only messages whose key has not been processed

kstream.filterNot((key, action) -&amp;gt; {
    boolean duplicate = KEYS.getIfPresent(key) != null;
    KEYS.put(key);
    return duplicate;
});
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;This is a best effort strategy since if the Streams app dies, so does its cache, and the new app instance won’t be able to deduplicate the very first duplicate (but this should happen so rarely that it is acceptable).&lt;/p&gt;

&lt;h3 id=&quot;limiting-the-disk-usage&quot;&gt;Limiting the disk usage&lt;/h3&gt;

&lt;p&gt;Streams and Kafka rely on offsets when reading messages which guarantees that when things start again they pick up where they stopped. On top of that, when using aggregations, Streams stores a changelog of the operations in Kafka itself, meaning that also stateful operations (grouping, reducing, counting, etc.) will recover gracefully and efficiently from a stop or crash by starting from where they had stopped. This is really nice fault-tolerance feature.&lt;/p&gt;

&lt;p&gt;The changelog topic is used on every start to build a “local state” for stateful operations on the machine where Streams is running. &lt;a href=&quot;http://rocksdb.org/&quot;&gt;RocksDB&lt;/a&gt; manipulates that state internally. This local state resides partly in memory and partly on disk, all depending on the amount of data in question. Instances running on our Kubernetes cluster are stateless, so they do not keep data on disk between restarts. This means we lose the local state each time the app is shutdown. However, when the streams application starts up again it will rebuild this local state from the Streams internal changelog topic, so no data is lost. As a consequence, there are two things to consider:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;rebuilding of the local state on (re)start can take several minutes depending on the volume of data and how it is partitioned&lt;/li&gt;
  &lt;li&gt;if the app runs for a long time the disk usage will keep on increasing, so we need to use &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;TimeWindows&lt;/code&gt; to avoid the issue of never-ending growing local state&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Below is an example of how to limit data on disk by using &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;TimeWindows&lt;/code&gt;.&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;// count by buckets of 1 day
// and keep these buckets for 1 day in the local state

.groupByKey()
.count(TimeWindows.of(TimeUnit.DAYS.toMillis(1))
                  .until(TimeUnit.DAYS.toMillis(1)),
       &quot;count-store&quot;);
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;migrating-existing-data&quot;&gt;Migrating existing data&lt;/h3&gt;

&lt;p&gt;The Cassandra table storing our data was not structured in a way that made it easy and fast to export the existing data. Since we were running on an older version of Cassandra, we used the &lt;a href=&quot;https://github.com/Netflix/astyanax&quot;&gt;Astyanax&lt;/a&gt; library to migrate the existing data over to PostgreSQL. It allowed partitioning of the ring buffer and reading in parallel from each of these partitions for higher throughput. The included checkpointing mechaninsm helped us start where it left off each time the migration would fail. Astyanax did a good job reading and writeíng the billions of rows of data needed within an acceptable migration time window without overloading the running Cassandra clusters.&lt;/p&gt;

&lt;p&gt;The above proved to be quite tricky due to tombstone and compaction issues. Tombstones would often lead to queries timing out, so we first had to fix that. These problems were not, for the most part, visible in day to day use, so it was a surprise challenge when migrating data (which took quite a lot of time to do).&lt;/p&gt;

&lt;h3 id=&quot;integration-testing&quot;&gt;Integration testing&lt;/h3&gt;

&lt;p&gt;We use &lt;a href=&quot;http://junit.org/junit4/&quot;&gt;JUnit&lt;/a&gt; and spin up an embedded Kafka and &lt;a href=&quot;https://zookeeper.apache.org/&quot;&gt;Apache Zookeeper&lt;/a&gt;. Then we use producers and consumers to send and receive messages for the input and output topics that the Streams expects. Extra care should be taken to clear the state of the Streams between each test.
Integration testing has proven to be quite challenging because there are a lot of details to take into account.&lt;/p&gt;

&lt;p&gt;For manual testing when quickly prototyping something new, we both make use of our Kubernetes development environment, or a local &lt;a href=&quot;https://docs.docker.com/compose/&quot;&gt;Docker Compose&lt;/a&gt; configuration, and a bunch of scripts using the Kafka tools.&lt;/p&gt;

&lt;h3 id=&quot;reprocessing&quot;&gt;Reprocessing&lt;/h3&gt;

&lt;p&gt;When rolling out bug fixes or improvements, it may be necessary to reprocess the existing data. Reprocessing with Streams is sort of an artform - especially if you have non-idempotent processing. Sometimes it is possible to reset the app (with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;kafka-streams-application-reset&lt;/code&gt;) and just restart with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;auto.offset.reset&lt;/code&gt; to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;earliest&lt;/code&gt; to reprocess everything. This clears all of the app’s state, and depending on the case this can be acceptable or not.
Our aggregator can start in a special reprocessing mode that spools all the messages of our 90-days long lived topic and only processes a given time interval.&lt;/p&gt;

&lt;h3 id=&quot;error-diagnostic&quot;&gt;Error diagnostic&lt;/h3&gt;

&lt;p&gt;Figuring out &lt;strong&gt;when&lt;/strong&gt; the Streams output data is wrong is not always as easy as seeing a sudden spike on a monitoring graph or incoherent numbers. Figuring out &lt;strong&gt;why&lt;/strong&gt; the numbers do not add up can be even more challenging in that there is no way to query the input Kafka topic to investigate. The same could be said of our Cassandra “input table” to Hadoop which was not structured after a diagnostic query need either. Putting a Kafka Connect Source from our input topic to a form of storage that allows easy querying might enable easier diagnostics.&lt;/p&gt;

&lt;p&gt;However, we’re happy to say that we never experienced any bugs in Streams itself and the Kafka cluster have proven to be very reliable. The problems we have encountered have mostly been due to corrupted or duplicated input data.&lt;/p&gt;

&lt;h2 id=&quot;summary&quot;&gt;Summary&lt;/h2&gt;

&lt;p&gt;The previous solution required a lot of knowledge on top of knowing Kafka, mainly Cassandra (dealing with tombstone problems, compaction strategies, cluster management, old composite columns vs CQL etc.) and Hadoop (maintaining HDFS, operations, batch-writing etc.). Using Streams instead, there is no extra required knowledge. The only thing to know about is Kafka (and the Streams framework).&lt;/p&gt;

&lt;p&gt;We also end up with a less heterogeneous infrastructure to run with fewer clusters and fewer products. It ends up being just one Kafka cluster and a bunch of webapps. Which we have quite a lot of experience of maintaining over time.&lt;/p&gt;

&lt;p&gt;With Kafka being more and more widely used at FINN (as well as Streams) the knowledge can also be shared between all and promotes cooperation across the organization.&lt;/p&gt;
</content>
        
        <author>
            <name>Nicolas Yann Couturier</name>
            
        </author>
        
    </entry>
    
    <entry>
        <title>Flappy Cat</title>
        <link rel="alternate" type="text/html" href="https://tech.finn.no2017/07/19/flappy-cat/"/>
        <updated>2017-07-19T16:58:36+00:00</updated>
        <id>https://tech.finn.no/2017/07/19/flappy-cat</id>
        <content type="html">&lt;h2 id=&quot;why-make-a-flappy-cat-game&quot;&gt;Why make a Flappy Cat game?&lt;/h2&gt;
&lt;p&gt;The Idea was to make a Flappy Bird with motion tracking control for our JavaZone stand. Some quick research told us it should be possible. Since cats are assosiated with FINN, it of course became a Flappy Cat game. Come and try it at JavaZone in September.&lt;/p&gt;

&lt;p&gt;Made by search team.&lt;/p&gt;

&lt;h2 id=&quot;how-we-made-it&quot;&gt;How we made it&lt;/h2&gt;

&lt;p&gt;&lt;img src=&quot;/images/2017-07-19-flappy-cat/flappy-tutorial.gif&quot; alt=&quot;Flappy tutorial&quot; /&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 1:&lt;/strong&gt; Make the game. As neither of us are game developers, we found this &lt;a href=&quot;http://www.lessmilk.com/tutorial/flappy-bird-phaser-1&quot;&gt;flappy bird&lt;/a&gt; tutorial online that we decided to follow. This tutorial uses Phaser, an open source JavaScript/HTML5 game development framework. It was easy to follow and it explained all the steps in detail, so even the most back-end heavy developer in our team had no problem writing his first JavaScript game.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/2017-07-19-flappy-cat/phaser-logo.png&quot; alt=&quot;Phaser&quot; /&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 2:&lt;/strong&gt; Buy the motion tracker. We chose Kinect as we found videos on YouTube of people doing exactly what we wanted to do with the Kinect sensor. We only had one day to do this project, so this was not the time to reinvent the wheel.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/2017-07-19-flappy-cat/kinect.jpg&quot; alt=&quot;alt text&quot; /&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 3:&lt;/strong&gt; Tweaking one of the example apps that was included in the Kinect 2.0 SDK. We found an app that was made to do something when it detected a gesture. This was perfect for our purpose. We recorded our desired gesture, “the flap”, in Kinect Studio and used Visual Gesture Builder (also included in the SDK) to build a database of our gesture. We then used this gesture database in the example app and suddenly we could recognize a flap! Now, all we needed was for something to happen when a flap was registered.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/2017-07-19-flappy-cat/wave.gif&quot; alt=&quot;alt text&quot; /&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 4:&lt;/strong&gt; Creating a simple Node.js server that could not only serve our game, but also have an API endpoint and give the Windows app a way to notify the game that it detected a flap. This is a simple solution; a GET call to /flap is equivalent to pressing space in the game.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/2017-07-19-flappy-cat/flap-demo.gif&quot; alt=&quot;Flap demo&quot; /&gt;&lt;/p&gt;
</content>
        
        <author>
            <name>Pernille Celia Sethre, Gunn Skinderviken</name>
            
        </author>
        
    </entry>
    
    <entry>
        <title>WAI-ARIA and its true impact on assistive technologies</title>
        <link rel="alternate" type="text/html" href="https://tech.finn.no2017/06/07/wai-aria-and-its-true-impact-on-assistive-technologies/"/>
        <updated>2017-06-07T12:00:00+00:00</updated>
        <id>https://tech.finn.no/2017/06/07/wai-aria-and-its-true-impact-on-assistive-technologies</id>
        <content type="html">&lt;h2 id=&quot;1-introduction&quot;&gt;1. Introduction&lt;/h2&gt;
&lt;p&gt;This article is intended for web developers who want to improve their understanding of the effect that WAI-ARIA has on assistive technologies. It will therefore be assumed that you already have basic knowledge about terms such as ARIA and screen readers.&lt;/p&gt;

&lt;h3 id=&quot;11-generalize-screen-readers&quot;&gt;1.1. Generalize screen readers&lt;/h3&gt;
&lt;p&gt;From the perspective of web development, it serves no purpose to delve into technical details about specific brands of screen reading software. There are several reasons for this:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;We can’t programmatically detect which assistive technologies our users are using. There are several good reasons for this, including privacy concerns pertaining to confidential information regarding personal health and diagnostics.&lt;/li&gt;
  &lt;li&gt;Universal design means design for everyone, regardless of which type of assistive technology is being used.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Therefore we won’t talk about specific brands of screen reading software in this article. We will focus on best practices with universal design in mind.&lt;/p&gt;

&lt;h3 id=&quot;12-the-main-issues-with-aria&quot;&gt;1.2. The main issues with ARIA&lt;/h3&gt;
&lt;p&gt;Firstly, support for ARIA in assistive technologies is still partial at best. A consequence of this is that only a handful of ARIA features are reliable across different implementations, and that is if we don’t count older assistive technologies that have no support for ARIA at all. Therefore it becomes important to differentiate between ARIA features that we can reliably use for correcting accessibility issues versus ARIA features that we should only use for improving UX. I.e. to a certain degree we can accept that UX will differ depending on which screen reader is being used, but keeping parts of our applications inaccessible to everyone except those who are fortunate enough to use the right screen reader is unacceptable. Naturally this is something that will slowly but surely sort itself out as makers of assistive technologies continue to work on their ARIA implementations, but for the time being we ought to remain mindful of this.&lt;/p&gt;

&lt;p&gt;The second issue related to ARIA is that many developers who technically know how to use it aren’t aware of the actual effect it has on assistive technologies. This is probably the biggest root of widespread incorrect use of ARIA on the web today, which is a growing source of frustration for users of assistive technologies. This is definitely an issue that web developers can and should do something about, and a good step in the right direction is to read the rest of this article.&lt;/p&gt;

&lt;h2 id=&quot;2-screen-readers-and-virtual-user-interfaces&quot;&gt;2. Screen readers and virtual user interfaces&lt;/h2&gt;
&lt;p&gt;Many screen readers create an invisible virtual user interface for presenting web content. This concept typically involves a process where the screen reader reads the entire web document by accessing its DOM, then it uses that information to construct a virtual representation of the document that the user can browse through. The screen reader will route most of the keyboard input to functions in the virtual user interface where it will be used for navigational purposes. Different screen readers have different quick keys, but typical examples are H for jumping to next heading, Shift+H for jumping to the previous heading, digits 1 through 6 for jumping between headings at levels 1 through 6 respectively, F for form elements, T for tables, and so on. These virtual user interfaces provide the user with a consistent and familiar interface that is highly optimized for quick and convenient keyboard navigation. On the flip side, the web application won’t receive keyboard input from the user while this virtual browsing mode is engaged, which can be a real issue in situations where the application requires keyboard input for optimal interaction.&lt;/p&gt;

&lt;h3 id=&quot;21-native-interactive-widgets&quot;&gt;2.1. Native interactive widgets&lt;/h3&gt;
&lt;p&gt;The most obvious example of an interactive widget that requires keyboard input from the user is an edit field (HTML textarea and certain input types). When the user starts interacting with such an element, their screen reader will automatically disengage its virtual user interface and pass keyboard input on to the application. At this point the user may freely take advantage of all keyboard features offered by the application and the web browser, such as typing in the edit field, move the cursor around using Arrow keys, etc. No ARIA magic is needed to make this work, since both input and textarea elements are native HTML widgets that web browsers and screen readers know how to handle correctly.&lt;/p&gt;

&lt;p&gt;The select element is another example of a native HTML widget that web browsers provide keyboard navigation for. The user can use arrow keys for navigation between the options, page up and page down for quickly scrolling through the options, home and end for beginning and end of the dropdown, and alpha-numeric keys can be used to efficiently navigate to the options that match the input from the user. Again, screen readers will therefore automatically turn off the virtual user interface when the user starts interacting with this type of widget, and all this works right out-of-the-box because the select element is a native HTML widget.&lt;/p&gt;

&lt;h3 id=&quot;22-non-native-interactive-widgets&quot;&gt;2.2. Non-native interactive widgets&lt;/h3&gt;
&lt;p&gt;The same opportunity for automatic toggling of virtual user interfaces should also be applied to non-native interactive widgets that are expected to handle keyboard navigation. Examples include menus, list boxes, tree views, grids, and even modal dialog boxes. Why should all of these widgets process keyboard input? Firstly because some users rely on a keyboard for interaction. Not everyone can use a pointing device (such as a mouse). Secondly, some users want it, because keyboard navigation can be fast, efficient, and convenient for interaction with these types of widgets. Implementing keyboard navigation for custom interactive widget can improve UX for a surprisingly wide range of users.&lt;/p&gt;

&lt;h3 id=&quot;23-examples-of-common-keyboard-navigation-features&quot;&gt;2.3. Examples of common keyboard navigation features&lt;/h3&gt;
&lt;ul&gt;
  &lt;li&gt;Alpha-numeric keys for quick selection of items. For instance, if focus is on one of the grouped buttons in a dialog box, the user should be able to press e.g. the Y key to select the Yes button. And item matching should be implemented for list boxes, tree views, menus, radio buttons, and pretty much any widget that contains grouped text items that are available for selection.&lt;/li&gt;
  &lt;li&gt;Arrow keys. Up and Down for navigating between items. Left and Right for expanding and collapsing sub-menus and branches in treeviews. Up/Down for values and Left/Right to switch between year, month, and date in a date picker widget. Proper spatial navigation for grids. And users should be able to use arrow keys to move focus between the dismiss option in a dialog box (Yes, No, Cancel, etc.)&lt;/li&gt;
  &lt;li&gt;Home/End keys for moving focus to the first or the last item in a group of items.&lt;/li&gt;
  &lt;li&gt;Page Up/Page Down should be implemented as the equivalent of X amount of Arrow Up/Arrow Down key presses for faster browsing through the items. X could either be a fixed number, or you could make a formula to calculate X based on the size of the group.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;24-announcing-support-for-keyboard-navigation&quot;&gt;2.4. Announcing support for keyboard navigation&lt;/h3&gt;
&lt;p&gt;We need to let screen readers know that we have taken the time to implement proper keyboard navigation for our non-native interactive widgets, otherwise screen readers will inadvertently hide this functionality behind their virtual user interfaces. It is true that these virtual user interfaces are effective for overall page navigation, but custom keyboard navigation is often even more effective when interacting with specific widgets. Below you’ll find three methods of using ARIA to help screen readers with automatic toggling of virtual user interfaces for non-native interactive widgets that support keyboard navigation.&lt;/p&gt;

&lt;h4 id=&quot;241-method-1-defining-roles&quot;&gt;2.4.1. Method 1: Defining roles&lt;/h4&gt;
&lt;p&gt;The WAI-ARIA specification lists many possible roles for HTML elements, and modern screen readers will handle presentation and interaction of elements according to the designated role. Screen readers will typically turn off the virtual user interface when the user starts interacting with elements that have any of the following roles: combobox, grid, listbox, menu, menubar, radiogroup, scrollbar, slider, spinbutton, tablist, textbox, tree, and treegrid.
IMPORTANT! You MUST NOT set any of these roles unless the widget has a functional mechanism for keyboard navigation implemented, otherwise the user will be much better off with navigation features provided by the screen reader. These ARIA roles are not just meant to be used for announcing the presence of something that visually resembles a certain widget. Their purpose is to prepare assistive technologies to accommodate the type of interaction that users associate with the type of widget in question, and naturally this requires that the widget itself keeps the promise of providing methods for keyboard navigation. E.g. a tree view must not just look like a tree view, it must also behave like one. Just like a doctor must not just look like a doctor. In short, we have certain expectations about entities that have specific roles.&lt;/p&gt;

&lt;p&gt;ARIA roles might also affect how screen readers will present the content of a widget to the user. A tree view for instance might be represented in a virtual user interface as a single starting point for interaction, effectively hiding the content of the tree so that it won’t clutter the virtual user interface with a lot of data. The user could then choose to interact with the tree if they want to browse through its items, but if keyboard navigation has not been implemented then the tree will essentially end up being totally inaccessible to the user since they can’t access the content of the tree in the virtual user interface either. So, let me emphasize that you MUST NOT set any of the ARIA roles for interactive widgets listed above unless you have also implemented a mechanism for keyboard navigation.&lt;/p&gt;

&lt;h4 id=&quot;242-method-2-using-the-aria-haspopup-property&quot;&gt;2.4.2. Method 2: Using the aria-haspopup property&lt;/h4&gt;
&lt;p&gt;This method can be used in conjunction with ARIA roles to aid screen readers with automatic deactivation of virtual user interfaces when an interactive widget pops up (like an overlay) in response to an action from the user. E.g. the user clicks on a button, and the expected result is that a menu pops up. Obviously the most convenient solution here from the user’s perspective would be to make the screen reader automatically initiate interaction with the popup widget, since the user has already implied that they would like to interact with the widget by clicking on the button that produced it.&lt;/p&gt;

&lt;p&gt;By setting aria-haspopup on an element, we promise two things:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;The application takes responsibility of moving focus to the starting point / default selectable element of the pop-up widget when the user starts interacting with the element that has aria-haspopup (e.g. by clicking on it).&lt;/li&gt;
  &lt;li&gt;The pop-up widget is accessible to users of assistive technology (i.e. keyboard navigation has been properly implemented for the pop-up widget).&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Screen readers may then automatically deactivate the virtual user interface to facilitate optimal usability with the widget. And again, this means that the pop-up widget MUST be accessible with a keyboard in the first place. Think of the aria-haspopup property as a promise of implemented accessibility features. It does not make the pop-up widget more accessible by itself.&lt;/p&gt;

&lt;p&gt;A few more things to keep in mind regarding aria-haspopup:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;The mechanism for activating the element that has aria-haspopup MUST be accessible for everybody. This means that you can’t just rely on e.g. a mouse-over event to trigger your pop-up menus, since many users of screen readers can’t successfully use a mouse even if they had one. Use click or focus events instead/in addition.&lt;/li&gt;
  &lt;li&gt;The element that has aria-haspopup must have an interactive role (either implied by HTML semantics or set with the role attribute), otherwise screen readers might simply choose to ignore the aria-haspopup property altogether. The exception here is the link role (or the native a element), which might cause the screen reader to retain its virtual user interface upon activation in anticipation of browser navigation. Remember that you can always override implied semantics with the role attribute, and the button role is usually a good choice for a clickable element that triggers a pop-up widget in the application.&lt;/li&gt;
  &lt;li&gt;The pop-up widget must also have correct semantics. I.e. give the widget an ARIA role that semantically corresponds to the type of widget that is specified in the aria-haspopup property. ARIA 1.1 defines the following possible values for aria-haspopup: dialog, grid, listbox, menu, and tree. E.g. use a value of “dialog” even if the pop-up widget has its role set to “alertdialog”, not just if its role is “dialog”. Also, for backward compatibility with ARIA 1.0, a value of “true” for the aria-haspopup property is equivalent to “menu”. Therefore, if your pop-up widget is a menu, then you might get wider coverage by using “true” instead of “menu”.&lt;/li&gt;
&lt;/ul&gt;

&lt;h4 id=&quot;243-method-3-the-application-role&quot;&gt;2.4.3. Method 3: The application role&lt;/h4&gt;
&lt;p&gt;Setting the application role on an element tells screen readers that the content of the element is not intended for virtual user interfaces. The application claims responsibility of providing adequate keyboard navigation features for all interactive widgets that are part of an element that has the application role.
IMPORTANT! You should think very carefully before using this method. Many users prefer to use the screen reader’s virtual user interface for overall navigation since it provides a consistent and familiar way of browsing web applications. Setting the application role (e.g. on the body element) is like blocking the highway and forcing drivers to use an alternative route where directions might be scarce. Unless you have a very specific reason to do so, you should refrain from doing it.&lt;/p&gt;

&lt;h2 id=&quot;3-defining-relations-between-html-elements&quot;&gt;3. Defining relations between HTML elements&lt;/h2&gt;
&lt;p&gt;ARIA provides three different properties for explicitly defining relations between HTML elements in cases where the DOM hierarchy does not already do this implicitly (by nesting and ordering elements in a sequence that makes sense from an end user’s perspective).&lt;/p&gt;

&lt;h3 id=&quot;31-the-aria-owns-property&quot;&gt;3.1. The aria-owns property&lt;/h3&gt;
&lt;p&gt;The aria-owns property is used to rearrange elements in the accessibility tree (which is a version of the DOM tree that has been tailored to accommodate assistive technologies). Of the three properties for specifying relations between elements, aria-owns is preferred for the following reasons:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Aria-owns is the most supported property for specifying relations between elements.&lt;/li&gt;
  &lt;li&gt;No special interaction is required for aria-owns to take effect. The user won’t need any additional knowledge to benefit from this feature.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;As an example of a scenario where aria-owns would be useful, let’s say we have a list where each item consists of a checkbox, an image, a heading, a paragraph, and a link. We might prefer to keep these elements in that particular order in the DOM for visual styling purposes. For someone who uses a screen reader however, this order could easily end up being confusing since the heading is not the first element. The heading is the element that introduces a new context to the user. Therefore the user wouldn’t know the context of the checkbox the first time they spot it. The second issue with this order is that the user might believe that the checkbox belongs to the previous list item since it’s technically under the heading of the previous list item. To fix this, we could create an empty div element at the top of each list item, and set aria-owns to refer to the id of the heading. This would alter the accessibility tree so that the heading would come before the other elements.&lt;/p&gt;

&lt;h3 id=&quot;32-the-aria-flowto-property&quot;&gt;3.2. The aria-flowto property&lt;/h3&gt;
&lt;p&gt;Instead of rearranging the DOM hierarchy for assistive technologies (like aria-owns does), the aria-flowto property tells assistive technologies to present the user with an option to jump directly to the related element(s). Unfortunately this property is not widely supported by assistive technologies. Another issue with aria-flowto is that users might not know how to trigger the jumps, even if their assistive technologies support this feature.&lt;/p&gt;

&lt;h3 id=&quot;33-the-aria-controls-property&quot;&gt;3.3. The aria-controls property&lt;/h3&gt;
&lt;p&gt;The aria-controls property is defined by the WAI-ARIA specification as a way of indicating which element(s) are being controlled by the element that has aria-controls set. In practice it provides a jump point (similar to aria-flowto), but it also indicates that interaction with the element that has aria-controls might have an effect on the controlled element(s). Consequently this means that aria-controls must only be added to interactive elements. Unfortunately, just like with aria-flowto, few assistive technologies support this property, and it also requires that the user knows how to initiate the jumps to take advantage of this.&lt;/p&gt;

&lt;p&gt;Interestingly enough, numerous web sites seem to be richly seasoned with aria-controls, and the golden rule about using ARIA to define relationships only when it can not be inferred from the DOM hierarchy is often forgotten. In cases where this feature is needed, aria-owns would be a better choice since it is more widely supported. In cases where this feature is superfluous, it just adds extra noise for those who use assistive technologies that support it. E.g. it makes no sense to create a jumping point from a button to a panel if the button is immediately followed by the panel in the DOM hierarchy. The user would spend more time and effort initiating the jump with their screen reader than it would be to just continue with the normal and familiar flow of navigation that would yield the same result.&lt;/p&gt;

&lt;h2 id=&quot;4-external-resources&quot;&gt;4. External resources&lt;/h2&gt;
&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;https://www.w3.org/TR/wai-aria-1.1/&quot;&gt;w3.org – WAI-ARIA 1.1&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://wiki.mozilla.org/Accessibility/Virtual_buffer_smash&quot;&gt;MozillaWiki – Virtual buffer smash&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://www.heydonworks.com/article/aria-controls-is-poop&quot;&gt;HeydonWorks – Aria-Controls is Poop&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</content>
        
        <author>
            <name>Tor-Martin Storsletten</name>
            
        </author>
        
    </entry>
    
    <entry>
        <title>Unleash Your Features Gradually</title>
        <link rel="alternate" type="text/html" href="https://tech.finn.no2017/03/10/unleash-your-features-gradually/"/>
        <updated>2017-03-10T16:58:36+00:00</updated>
        <id>https://tech.finn.no/2017/03/10/unleash-your-features-gradually</id>
        <content type="html">&lt;p&gt;&lt;a href=&quot;https://www.finn.no&quot;&gt;FINN.no&lt;/a&gt; is the largest online marketplace in Norway, and we take continuous deployment seriously. We are about one hundred developers deploying new code to production 978 times each week. That is 978 / 100 = 9.78 deployments to production per developer every week. In order to get to these numbers and still keep it under control, we have had to both change our development process and establish the right set of tools. In this article we introduce feature toggles and how this technique has contributed to our continuous deployment success.&lt;/p&gt;

&lt;p&gt;We will also present &lt;a href=&quot;https://github.com/Unleash/unleash&quot;&gt;Unleash, an open source framework&lt;/a&gt; we created to support feature toggles at an enterprise scale. Unleash allows us to enable features for specific users via the activation strategies concept. Unleash is already set up and in use by multiple teams within Schibsted, including FINN, SPT Payment, and Knocker.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Unleash is now also available as a Software-as-a-Service offering, making it much easier for anyone to adopt. Check it out at &lt;a href=&quot;https://www.unleash-hosted.com/&quot;&gt;unleash-hosted.com&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;img class=&quot;center-block&quot; src=&quot;/images/2017-03-10-unleash-your-features-gradually/1-finn-releases.png&quot; alt=&quot;alt&quot; title=&quot;Number of production deploys in “week 50” in recent years&quot; /&gt;&lt;/p&gt;
&lt;center&gt;&lt;i&gt;Number of production deploys in “week 50”&lt;/i&gt;&lt;/center&gt;

&lt;p&gt;&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;

&lt;h2 id=&quot;feature-toggles&quot;&gt;Feature Toggles&lt;/h2&gt;
&lt;p&gt;‘Feature toggles’ is a simple technique to separate the process of putting new code into production from the process of releasing new features to our users. In its simplest form a feature toggle is just a “if” statement in the code, guarding the new feature:&lt;/p&gt;

&lt;div class=&quot;language-java 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;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;unleash&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;isEnabled&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;AwesomeFeature&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;))&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;c1&quot;&gt;//magic new feature code&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;c1&quot;&gt;//old boring stuff&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The fastest way to get started with feature toggles in your project is to use a plain old &lt;a href=&quot;https://docs.oracle.com/javase/tutorial/essential/environment/properties.html&quot;&gt;properties file&lt;/a&gt;. This allows you to integrate unfinished features to the master branch and hide them from users in production. A simple plain properties files was exactly how we started playing with the features toggles in FINN.&lt;/p&gt;

&lt;p&gt;We realised that feature toggles enabled us to move faster, because we were able to integrate new and unfinished features to the master branch early. This also avoids long-running feature branches that tend to be harder to merge. We learned the hard way that having many parallel feature branches adds a lot of extra complexity related to merging, but with the help of feature toggles we can merge them directly to master.&lt;/p&gt;

&lt;p&gt;This went well, but we also felt we could do better. We wanted one simple dashboard to turn the feature toggles on and off. The dashboard should contain all feature toggles for all web apps and applications in FINN. After creating several  proof-of-concepts using various technologies, we proposed a solution to our technology management group. They loved the idea and we created &lt;a href=&quot;https://github.com/Unleash/unleash&quot;&gt;Unleash&lt;/a&gt;, our shiny and open source feature toggle system.&lt;/p&gt;

&lt;p&gt;Unleash gives us a great overview of all feature toggles across all our services and applications. It also makes it super easy to activate new features in real time in production and of course instantaneous deactivation of  the feature if there’s a problem.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/2017-03-10-unleash-your-features-gradually/2-unleash-dashboard.png&quot; alt=&quot;alt text&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;system-overview&quot;&gt;System Overview&lt;/h2&gt;

&lt;p&gt;Unleash is comprised of three parts:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;Unleash API&lt;/strong&gt; - The service holding all feature toggles and their configurations. Configurations declare which activation strategies to use and which parameters they should get.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Unleash UI&lt;/strong&gt; - The dashboard used to manage feature toggles, define new strategies, look at metrics, etc.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Unleash SDK&lt;/strong&gt; - Used by clients to check if a feature is enabled or disabled. The SDK also collects metrics and sends them to the Unleash API. Activation Strategies are also implemented in the SDK. Unleash currently provides official SDKs for Java and Node.js&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;img class=&quot;center-block&quot; src=&quot;/images/2017-03-10-unleash-your-features-gradually/3-unleash-diagram.png&quot; title=&quot;Unleash system overview&quot; alt=&quot;Unleash system overview&quot; /&gt;&lt;/p&gt;
&lt;center&gt;&lt;i&gt;Unleash system overview&lt;/i&gt;&lt;/center&gt;

&lt;p&gt;Unleash is written with performance and resilience in mind.&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;Performance&lt;/strong&gt; - In order to be super fast, the client SDK caches all feature toggles and their current configuration in memory. The activation strategies are also implemented in the SDK. This makes it really fast to check if a toggle is on or off because it is just a simple function operating on local state, without the need to poll data from the database.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Resilience&lt;/strong&gt; - If the unleash API becomes unavailable for a short amount of time, the cache in SDK will minimise the effect. The client will not be able get updates, when the API is unavailable, but the SDK will keep running with the last known state.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Built in backup&lt;/strong&gt; - The SDK also persists the latest known state to a local file at the instance where the client is running. It will persist a local copy every time the client detects changes from the API. Having a local backup of the latest known state minimises the consequence of clients not being able to to talk to Unleash API at startup. This is required because network is unreliable.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;gradual-roll-out-of-features&quot;&gt;Gradual Roll Out of Features&lt;/h2&gt;
&lt;p&gt;It is powerful to be able to turn a feature on and off instantaneously without redeploying the whole application. The next level of control comes when you are able to enable a feature for a specific user or a small subset of the users. We achieve this level of control with the help of activation strategies. The simplest strategy is the “default” strategy, which basically means that the feature should be enabled for everyone.&lt;/p&gt;

&lt;p&gt;Another common strategy is to enable a new feature only for specific users. Typically I want to enable a new feature only for myself in production, before I enable it for everyone else. To achieve this we can use the “UserWithIdStrategy”. This strategy allows you to specify a list of specific user ids that you want to activate the new feature for. &lt;em&gt;(A user id may of course be an email if that is more appropriate in your system.)&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;img class=&quot;center-block&quot; src=&quot;/images/2017-03-10-unleash-your-features-gradually/4_user_with_id.png&quot; alt=&quot;4_user_with_id&quot; /&gt;&lt;/p&gt;

&lt;p&gt;When I have verified that the new feature works as expected in production, I can activate the feature for some real users. To achieve this I can use one of the gradual rollout strategies. At FINN we use three variants of gradual rollout, which have slightly different purposes:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;GradualRolloutUserId&lt;/strong&gt; is the strategy we use when we want to gradually rollout a new feature to our logged-in users. This strategy guarantees that the same user gets the same experience every time, across devices. It also guarantees that a user from the first 10% will also be part of the first 20% of the users. Thus we ensure that users get the same experience. Even if we gradually increase the number of users who are exposed to a particular feature. To achieve this we hash the user id and normalise the hash value to a number between 1 and 100 with a simple modulo operator.&lt;/p&gt;

&lt;p&gt;&lt;img class=&quot;center-block&quot; src=&quot;/images/2017-03-10-unleash-your-features-gradually/5_hash_and_normalise.png&quot; alt=&quot;5_hash_and_normalise&quot; /&gt;&lt;/p&gt;
&lt;center&gt;&lt;i&gt;Converting a user id string to a number between 1 and 100&lt;/i&gt;&lt;/center&gt;
&lt;p&gt;&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;GradualRolloutSessionId&lt;/strong&gt; is almost identical to the previous strategy, with the exception that it works on session ids. This makes it possible to target all users (not just logged in users), guaranteeing that a user will get the same experience within a session.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;GradualRolloutRandom&lt;/strong&gt; has no form of user stickiness, it just picks a random number for each “isEnabled” call. We have found this rollout strategy to be very useful in some scenarios when we are enabling a feature which is not visible to the end user. Because of its randomness it becomes a great way to sample metrics and error reports and it can be a useful way to detect JavaScript errors in the browser.&lt;/p&gt;

&lt;p&gt;&lt;img class=&quot;center-block&quot; src=&quot;/images/2017-03-10-unleash-your-features-gradually/6_gradual_rollout_random.png&quot; alt=&quot;gradual_rollout_random.png&quot; /&gt;&lt;/p&gt;

&lt;p&gt;In Unleash 2.0 we added support for multiple strategies. This is especially convenient when you both want to expose your new shiny feature to a percentage of the users and at the same time make sure to expose it to yourself.&lt;/p&gt;

&lt;h2 id=&quot;metrics&quot;&gt;Metrics&lt;/h2&gt;
&lt;p&gt;When you enable a feature for a small group of users it is nice to know how many users actually get exposed to it. This is why we added metrics to Unleash 2.0. The SDK will locally collect metrics in the background and regularly share this information with the Unleash API. The API then aggregates the metrics from all clients and calculates how many times a feature toggle evaluated to enabled in a given time period.&lt;/p&gt;

&lt;p&gt;&lt;img class=&quot;center-block&quot; src=&quot;/images/2017-03-10-unleash-your-features-gradually/7_metrics.png&quot; alt=&quot;metrics&quot; /&gt;&lt;/p&gt;

&lt;p&gt;When implementing the metrics feature we discovered that this was a nice way to display which toggles are actually in use in an application. It even made it possible to show feature toggles being used in the client code which are not defined in the Unleash API. Showing undefined toggles used in client applications makes it easier to discover feature toggles not defined in Unleash UI. This can happen when a developer starts using a new feature toggle, but has not defined it in Unleash UI yet. The undefined feature toggle is also a clickable link to  make it easy to define it in Unleash.&lt;/p&gt;

&lt;p&gt;&lt;img class=&quot;center-block&quot; src=&quot;/images/2017-03-10-unleash-your-features-gradually/8_undefined_toggle.png&quot; alt=&quot;undefined&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;types-of-toggles&quot;&gt;Types of Toggles&lt;/h2&gt;
&lt;p&gt;We have seen how release toggles allow us to decouple deployment of code from the release of new features. At FINN we have ended up with three main categories of feature toggles:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Release toggles&lt;/strong&gt; are the most common type of feature toggles we use. These are used for new features we roll out in the market and are mainly used to roll them out safely:  instead of exposing a new feature to everyone at once we can try it out on a small subset of our users and verify that it scales, does not contain bugs, and that it moves our KPIs in the right direction.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Kill switches&lt;/strong&gt; are sometimes necessary to protect our site. Like many other large sites we occasionally have some features that struggle under certain edge cases, often related to third party integrations. The Kill switches allow us to turn these features off, which is a simple way to degrade our service in order to keep the business running. Of course it would be nice to not need the kill switches, but they have proven their value over time.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Business toggles&lt;/strong&gt; are used to turn on certain features for specific users, segments or customers. It’s pretty straightforward to solve this in Unleash because of the flexible activation strategies, but we generally don’t use Unleash for this. It’s considered acceptable for experimenting with new ways to segment our service, but we don’t want the business toggles to become a permanent part of Unleash. We think permanent business rules should be handled by the application code and not Unleash.&lt;/p&gt;

&lt;h2 id=&quot;tips-and-best-practices&quot;&gt;Tips and Best Practices&lt;/h2&gt;
&lt;p&gt;We have been using feature toggles for several years in FINN. Here are some tips about how to use them more effectively:&lt;/p&gt;

&lt;h3 id=&quot;feature-toggles-increase-technical-debt&quot;&gt;Feature Toggles Increase Technical Debt&lt;/h3&gt;
&lt;p&gt;One of the biggest issue we have found is that due to ease of use developers love to add new feature toggles but don’t seem to remove them afterwards. Sometimes developers keep them around because they feel they are “nice to have”. Other times they are just forgotten or no one has the time to refactor and remove them. A feature toggles is technical debt from the moment it is added to the code.  The general advice is to remove the toggle as soon as it has served its purpose. Every feature toggle adds a new code path through the application making it harder to test and debug the code.&lt;/p&gt;

&lt;h3 id=&quot;dont-use-feature-toggles-if-you-dont-need-them&quot;&gt;Don’t Use Feature Toggles if You Don’t Need Them&lt;/h3&gt;
&lt;p&gt;In many cases you can safely add a new and unused feature to the application without protecting it with a feature toggle. It might be a new API endpoint or a new page on a separate URL, either way a feature toggle is not needed.&lt;/p&gt;

&lt;p&gt;If you have to protect your new feature you should try to use as few if-statements as possible, ideally just one if-statement should guard the feature.&lt;/p&gt;

&lt;h2 id=&quot;further-plans-for-unleash&quot;&gt;Further Plans for Unleash&lt;/h2&gt;
&lt;p&gt;Unleash has been actively used in production by FINN from 2014. The project has been lucky to have many different &lt;a href=&quot;https://github.com/Unleash/unleash/graphs/contributors&quot;&gt;contributors&lt;/a&gt; over the years.&lt;/p&gt;

&lt;p&gt;Currently &lt;a href=&quot;https://github.com/ivarconr&quot;&gt;Ivar&lt;/a&gt;, &lt;a href=&quot;https://github.com/sveisvei&quot;&gt;Sveinung&lt;/a&gt; and &lt;a href=&quot;https://github.com/vsandvold&quot;&gt;Vegard&lt;/a&gt; actively maintain the project. We have some great ideas for the future and we hope to see some of them implemented. In this section we will briefly explain some of these ideas. We would love to hear your feedback on them!&lt;/p&gt;

&lt;h3 id=&quot;unleash-as-saas&quot;&gt;Unleash as SaaS&lt;/h3&gt;
&lt;p&gt;Currently you have to host your own instance of the API and the UI in order to use Unleash. This includes setting up a database and running a node application in production. We believe it could greatly improve adoption if Unleash was provided as a software as a service (SaaS). It would of course require us to add some access control, but this is something we have on the roadmap anyway. This might require us to charge the users of Unleash for a monthly cost and spend that money on hosting and new development.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Update April 2019&lt;/strong&gt;
We finally did this and you can now check out our SaaS offering called Unleash-Hosted at &lt;a href=&quot;https://www.unleash-hosted.com/&quot;&gt;https://www.unleash-hosted.com&lt;/a&gt;!&lt;/p&gt;

&lt;h3 id=&quot;and-strategies&quot;&gt;“AND Strategies”&lt;/h3&gt;
&lt;p&gt;Unleash 2.0 added support for multiple strategies. This was a huge improvement! But it is implemented as a simple array where all the strategies are “OR”-ed in order to determine if a feature should be enabled. We believe we could improve this by adding support for required strategies. This would make it possible to combine strategies more freely to create new segmented roll-out groups. For example, this would make it trivial to only gradually roll out a feature toggle to our beta users.&lt;/p&gt;

&lt;h3 id=&quot;authentication-and-access-control&quot;&gt;Authentication and Access Control&lt;/h3&gt;
&lt;p&gt;The Unleash service already provides hooks for registering your own middleware, so that you can actually add your own access control. But why not add a few ready to use implementations, such as Google Authentication or Okta? This would make it much easier to integrate into an enterprise already using an identity provider.&lt;/p&gt;

&lt;h3 id=&quot;application-scoped-toggles&quot;&gt;Application Scoped Toggles&lt;/h3&gt;
&lt;p&gt;Today all applications receive all toggles for everyone in the same cluster. In Unleash 2.0 we added a client registration feature, where all clients register with the API using a unique identifier. We could use the application identifiers to provide a simple way to define which application you intend to use a feature toggle for. This would make it possible to only expose relevant feature toggles to relevant applications.&lt;/p&gt;

&lt;h3 id=&quot;time-to-live&quot;&gt;Time to Live&lt;/h3&gt;
&lt;p&gt;Because most features toggles should be used for a limited time it would be nice to be able to set a time-to-live field on them. Then Unleash UI could then inform developers about toggles they use which have expired, which would be a great motivator to clean up feature toggles which have served their purpose.&lt;/p&gt;

&lt;h3 id=&quot;ab-testing&quot;&gt;A/B Testing&lt;/h3&gt;
&lt;p&gt;At FINN we use Unleash as a way to setup and manage simple A/B tests. Our current implementation is a bit cumbersome and we believe there is room for generalising and standardising this, making it available to anyone. Also, in order to support multivariate experiments we might need to change the API slightly.&lt;/p&gt;

&lt;p&gt;These are just some of the ideas we have for Unleash. Which features would you like to see in Unleash? I would love to hear from you!&lt;/p&gt;

&lt;h2 id=&quot;summary&quot;&gt;Summary&lt;/h2&gt;
&lt;p&gt;In this article we introduced feature toggles and how FINN uses Unleash to gradually release new features. This approach gives us more control because it decouples the process of putting new code into production from the release of new features for our users.&lt;/p&gt;

&lt;p&gt;Sound interesting? Check out the &lt;a href=&quot;https://github.com/Unleash/unleash/blob/master/docs/getting-started.md&quot;&gt;getting started guide&lt;/a&gt;. If you need help please do not hesitate file an issue or reach out to any of the &lt;a href=&quot;https://github.com/Unleash/unleash/graphs/contributors&quot;&gt;core contributors&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Thanks to everyone who has contributed to the project. All contributions are highly appreciated.&lt;/strong&gt;&lt;/p&gt;
</content>
        
        <author>
            <name>Ivar Conradi Østhus</name>
            
        </author>
        
    </entry>
    
    <entry>
        <title>Åpen fagkveld 7. mars</title>
        <link rel="alternate" type="text/html" href="https://tech.finn.no2017/02/09/fagkveld/"/>
        <updated>2017-02-09T14:14:30+00:00</updated>
        <id>https://tech.finn.no/2017/02/09/fagkveld</id>
        <content type="html">&lt;h2 id=&quot;åpen-fagkveld-7-mars-hjemme-hos-finn---kl-15-20&quot;&gt;Åpen fagkveld 7. mars hjemme hos FINN - kl 15-20&lt;/h2&gt;

&lt;p&gt;For ett år siden åpnet vi dørene hjem til oss i Grensen for å dele våre erfaringer rundt hvordan vi jobber med produktutvikling. Vi fikk gode tilbakemeldinger på kvelden og nå ønsker vi å invitere til en ny fagkveld. Denne gangen blir agendaen mer rettet mot teknologien. Vi viser dere hvordan vi bygger og holder vedlike et av norges mest besøkte nettsteder.&lt;/p&gt;

&lt;h2 id=&quot;program&quot;&gt;Program&lt;/h2&gt;

&lt;table&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;15:30&lt;/td&gt;
      &lt;td&gt;&lt;strong&gt;Velkommen til FINN&lt;/strong&gt; ved Anders Skoe (adm.dir)&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;15:45&lt;/td&gt;
      &lt;td&gt;&lt;strong&gt;“Slik jobber vi i FINN”.&lt;/strong&gt; 3 lyntaler som bør gi deg en bedre forståelse av hvordan vi jobber i FINN. Vi kommer blant annet innom organisering, strategi, arkitektur, devops, testing og kultur.&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;17:00&lt;/td&gt;
      &lt;td&gt;&lt;strong&gt;Introducing Node.js in an Enterprise.&lt;/strong&gt; In this talk we will introduce Node.js and how we introduce it to a large enterprise in a safe way.&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;17:30&lt;/td&gt;
      &lt;td&gt;&lt;strong&gt;Beyond “Hello World”, handling complexity and organization in large systems.&lt;/strong&gt; How architecture and organization come together to address the challenges we face at FINN.no. How we believe decentralised ownership and decision making can help improve development speed and product quality over time.&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;18:15&lt;/td&gt;
      &lt;td&gt;&lt;strong&gt;Maskinlæring, anbefalingsalgoritmer og datadrevne produkter&lt;/strong&gt; Hvordan bruker FINN.no sine data til å lage gode produkter for brukerne sine? Hvilke datadrevne produkter har vi, hvordan lager vi anbefalinger for brukeren og hvordan forbedres algoritmene bak.&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;18:45&lt;/td&gt;
      &lt;td&gt;&lt;strong&gt;Hvordan vi flytter FINN.no ut i skyen&lt;/strong&gt; Vi jobber for tiden med å migrere mikrotjenestene våre til en mer moderne, dynamisk skalerbar infrastruktur basert på Kubernetes. Hvordan gjør vi det, og hvordan sørger vi for at vi håndterer brukertrafikk og kan fortsette å levere endringer til prod 1000 ganger i uka mens vi migrerer.&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;Takk til alle som kom for en hyggelig kveld.&lt;/p&gt;

&lt;h2 id=&quot;presentasjoner&quot;&gt;Presentasjoner&lt;/h2&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;/images/2017-03-07-fagkveld-presentasjoner/Slik jobber vi i FINN ... organisering &amp;amp; kultur.pdf&quot;&gt;Slik jobber vi i FINN - organisering og kultur&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;/images/2017-03-07-fagkveld-presentasjoner/Slik jobber vi i FINN … prosess.pdf&quot;&gt;Slik jobber vi i FINN - prosess&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;/images/2017-03-07-fagkveld-presentasjoner/Slik jobber vi i FINN ... IT-Strategi @FINN.no.pdf&quot;&gt;Slik jobber vi i FINN - IT-Strategi&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;/images/2017-03-07-fagkveld-presentasjoner/Introducing Node.js in an Enterprise.pdf&quot;&gt;Introducing Node.js in an Enterprise - Node&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;/images/2017-03-07-fagkveld-presentasjoner/finn.node.modulized/index.html&quot;&gt;Introducing Node.js in an Enterprise - Application modularity&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;/images/2017-03-07-fagkveld-presentasjoner/Beyond Hello World.pdf&quot;&gt;Beyond “Hello World&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;/images/2017-03-07-fagkveld-presentasjoner/MachineLearning@finn.pdf&quot;&gt;Maskinlæring, anbefalingsalgoritmer og datadrevne produkter&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://docs.google.com/presentation/d/1Yy7VswgxZb6olZtGhXN3fRFR6Hk1dXWb1a60mihnWsY/edit#slide=id.g16e8500e57_0_269&quot;&gt;Hvordan vi flytter FINN.no ut i skyen&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;img src=&quot;/images/2017-03-07-fagkveld-images/oversikt2.jpeg&quot; alt=&quot;alt text&quot; title=&quot;Om Node.js&quot; /&gt;&lt;/p&gt;
</content>
        
        <author>
            <name>Gunn Skinderviken, Henning Spjelkavik</name>
            
        </author>
        
    </entry>
    
    <entry>
        <title>Category guessing</title>
        <link rel="alternate" type="text/html" href="https://tech.finn.no2017/02/02/category-guessing/"/>
        <updated>2017-02-02T14:14:30+00:00</updated>
        <id>https://tech.finn.no/2017/02/02/category-guessing</id>
        <content type="html">&lt;h2 id=&quot;the-problem&quot;&gt;The problem&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Making it easy to fill out forms is essential for getting data into our system at &lt;a href=&quot;https://www.finn.no/smajobber&quot;&gt;FINN småjobber&lt;/a&gt;&lt;/strong&gt;, which is a service to get help with tasks like house cleaning or renovation, where the user submits a job that non-professionals bid on.&lt;/p&gt;

&lt;p&gt;Submitting a job is done by filling out a form with a title, a short description, and selecting the category the job belongs to. The category hierarchy has two levels: a main category and a subcategory. For example the main category &lt;em&gt;domestic help&lt;/em&gt;, has the subcategories cleaning and &lt;em&gt;babysitting&lt;/em&gt;. There are 6 main categories and 26 subcategories, and our assumption was that it was a hassle for the user to go through this long list. &lt;strong&gt;We wanted to simplify this by guessing the category.&lt;/strong&gt; Ideally on the given title.&lt;/p&gt;

&lt;h2 id=&quot;the-solution&quot;&gt;The solution&lt;/h2&gt;

&lt;h3 id=&quot;machine-learning&quot;&gt;Machine learning&lt;/h3&gt;
&lt;p&gt;A typical solution to this kind of problem is to use machine learning, where the goal is to classify job titles into categories. &lt;strong&gt;We had a large set of historical data (130 000 registered jobs) where category already was selected by the user.&lt;/strong&gt; This was a good starting point for a type of machine learning technique called supervised learning. &lt;strong&gt;In supervised learning historical data is used as a training set&lt;/strong&gt;, and results in a classifier that can classify job titles into categories.&lt;/p&gt;

&lt;p&gt;Delving into the field of machine learning is quite a complex task, and requires both theoretical  understanding and knowhow of various software libraries. And in many cases, it requires access to a special machine learning infrastructure. So, &lt;strong&gt;as a lean product development team with limited resources we looked for an easier solution&lt;/strong&gt;.&lt;/p&gt;

&lt;h3 id=&quot;good-old-search&quot;&gt;Good old search&lt;/h3&gt;

&lt;p&gt;Search was a sentral part of our service. All the jobs were indexed, and we had all the necessary libraries and infrastructure for this. So, &lt;strong&gt;what about using normal search to find the category?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;A simple search on the title “Cleaning kitchen”, gave the following ranked results:&lt;/p&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;Match rank&lt;/th&gt;
      &lt;th&gt;Category&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;1&lt;/td&gt;
      &lt;td&gt;Renovation&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;2&lt;/td&gt;
      &lt;td&gt;Cleaning&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;3&lt;/td&gt;
      &lt;td&gt;Cleaning&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;4&lt;/td&gt;
      &lt;td&gt;Renovation&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;5&lt;/td&gt;
      &lt;td&gt;Cleaning&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;6&lt;/td&gt;
      &lt;td&gt;Cleaning&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;7&lt;/td&gt;
      &lt;td&gt;Cleaning&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;8&lt;/td&gt;
      &lt;td&gt;Cleaning&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;9&lt;/td&gt;
      &lt;td&gt;Carpentry and assembly&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;10&lt;/td&gt;
      &lt;td&gt;Cleaning&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;Our first attempt was to simply &lt;strong&gt;select the highest ranked match&lt;/strong&gt;. This gave us 87% chance for guessing the main category correctly, and 68% chance of guessing the subcategory correctly.&lt;/p&gt;

&lt;p&gt;These probabilities where calculated in a simulation by guessing category for 10000 random jobs in our dataset, and comparing the guessed category with what was actually selected by the user.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;We were quite impressed by these results&lt;/strong&gt;, however, we wished to provide the users with even better guesses. When we analysed the search results for various job titles, we found that the correct category often was not the highest ranked match, but number 2 or 3, as shown in the table above.&lt;/p&gt;

&lt;p&gt;So based on this insight we came up with a simple algorithm that &lt;strong&gt;finds the most frequently represented categories among the 10* best matches&lt;/strong&gt;. This gave us the following results:&lt;/p&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;Category&lt;/th&gt;
      &lt;th&gt;Frequency&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;Cleaning&lt;/td&gt;
      &lt;td&gt;7&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Renovation&lt;/td&gt;
      &lt;td&gt;2&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Carpentry and assembly&lt;/td&gt;
      &lt;td&gt;1&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;&lt;strong&gt;It was safe to assume that the correct category was cleaning, as it is represented 7 times.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;We only wanted to make a guess when we were quite certain, and concluded that &lt;strong&gt;when a category is represented 6 or more times among the 10 best matches, we make a guess&lt;/strong&gt;. As for the rest, we leave it up to the user to manually select a category.&lt;/p&gt;

&lt;p&gt;This algorithm improved our guessing, and in our simulation gave us a &lt;strong&gt;95% chance of guessing main category correctly&lt;/strong&gt;, and &lt;strong&gt;85% chance of guessing subcategory correctly&lt;/strong&gt;.&lt;/p&gt;

&lt;h2 id=&quot;in-production&quot;&gt;In production&lt;/h2&gt;

&lt;p&gt;The following graph shows that we guess category for about 60% of all the jobs.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/2017-02-02-category-guessing/production.png&quot; alt=&quot;Success matches in production&quot; title=&quot;Success matches in production&quot; /&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The graph also shows that around 90% of the jobs are submitted with the the guessed category.&lt;/strong&gt; As for the remaining 10% of the guessed jobs, we assume that we have guessed wrong category, and that the user has changed the proposed category to one they found more suitable.&lt;/p&gt;

&lt;p&gt;What is interesting is that having the user correct our guesses, it will &lt;strong&gt;automatically improve our guesses over time&lt;/strong&gt;, as the given title will be associated with a more correct category.&lt;/p&gt;

&lt;h3 id=&quot;thanks-to&quot;&gt;Thanks to&lt;/h3&gt;
&lt;ul&gt;
  &lt;li&gt;Håvard Nesvold for proofreading and feedback.&lt;/li&gt;
  &lt;li&gt;The brilliant FINN småjobber team!&lt;/li&gt;
  &lt;li&gt;Simen Eide and Fredrik Jørgensen for good talks on machine learning.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;*) We experimented with the number of best matches to include in the calculation, but found that 10 gave the overall best results.&lt;/p&gt;
</content>
        
        <author>
            <name>Tor Arne Kvaløy</name>
            
            <email></email>
            
        </author>
        
    </entry>
    
    <entry>
        <title>WTF er Programmatic?</title>
        <link rel="alternate" type="text/html" href="https://tech.finn.no2016/12/15/programmatic/"/>
        <updated>2016-12-15T10:17:32+00:00</updated>
        <id>https://tech.finn.no/2016/12/15/programmatic</id>
        <content type="html">&lt;h2 id=&quot;-og-hvorfor-trenger-jeg-å-bry-meg-om-det&quot;&gt;… og hvorfor trenger jeg å bry meg om det?&lt;/h2&gt;

&lt;p&gt;For å forstå programmatisk kjøp og salg av annonser, er det viktig å forstå hva vi legger i begrepene. Hva skiller det eksempelvis fra tradisjonelt merkevaresalg? Er det ene alternativet bedre enn det andre?&lt;/p&gt;

&lt;p&gt;I korte trekk er det viktig å forstå forskjellene mellom direktesalg og programmatisk salg. Direktesalg omfatter alle salg som gjøres fra et menneske til et annet menneske, og er ofte relasjonsbasert. Toyota ringer selger Y med ønske om bannerannonsering på FINN. Selger Y lager et tilbud basert på kundens behov og målsetninger, får deretter godkjenning fra kunden, booker det inn i ROSE og får til slutt en annen medarbeider på Schibsted sin traffic-avdeling til å sette den avtalte annonsen LIVE i annonsestyringssystemet AppNexus.&lt;/p&gt;

&lt;p&gt;FINN sine annonseprodukter kan i all hovedsak deles inn i to hovedretninger, men begge starter med samme utgangspunkt. Annonsøren velger et banner, eksempelvis netboard. Dette banneret kan du deretter velge om du vil sette i en kontekstuell plassering på FINN, eksempelvis på et bilmerke, eller om du vil vise budskapet til en gitt målgruppe. Se illustrasjon nedenfor.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/2016-09-29-programmatic/targeting_kontekst.png&quot; alt=&quot;types&quot; title=&quot;types&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Programmatisk kjøp og salg er en form for automatisert kjøp av bannerannonser, hvor det i teorien er minimalt med menneskelig kontakt. Programmatisk er ikke et eget produkt, men heller en annen måte å kjøpe de overnevnte merkevareproduktene til FINN på.&lt;/p&gt;

&lt;p&gt;Det er heller ikke tilgjengelig for alle kundene våre. Hvorfor ikke? Jo, det fordrer at Toyota har tilgang til et teknikksystem som kalles «Demand Side Platform» (DSP), hvor de selv enkelt kan velge ønskede medier og målgrupper uten å involvere en selger. Kunden kan kjøpe disse medieplasseringene – og målgruppene - automatisert fordi mediene benytter seg av en teknisk motpart, nemlig en «Sell Side Platform» (SSP) til å tilgjengeliggjøre varelageret sitt i en såkalt annonsebørs. Når rett bruker dukker opp på rett tid, settes det i gang en «auksjon» hvor de ulike kundene (via sine DSP-er) har en budkrig for å nå drømmebrukeren sin. Den som vinner budkrigen får vise annonsen sin til brukeren. Se illustrasjon nedenfor.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/2016-09-29-programmatic/how_it_works.png&quot; alt=&quot;how_it_works&quot; title=&quot;how it works&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;hvorfor-kutter-vi-ikke-ut-salgsleddet-og-selger-kun-programmatisk&quot;&gt;Hvorfor kutter vi ikke ut salgsleddet og selger kun programmatisk?&lt;/h2&gt;

&lt;p&gt;Det er flere grunner til at programmatisk enn så lenge står for en liten del av totalinntektene til FINN Salg. Først og fremst koster en DSP penger, og det benyttes hovedsakelig av profesjonelle merkevarekjøpere i mediebyrå som håndterer såpass store reklameinvesteringer at de kan forsvare teknikk-kostnadene. En bilforhandler fra Oppdal har neppe råd til disse systemene, og vil nok heller ikke tjene på en slik investering. I tillegg er programmatisk kjøp og salg fremdeles ungt i Norge, det krever opplæring i systemene, og i praksis er det fortsatt mye menneskelig kontakt som må til for å få på plass en kampanje.&lt;/p&gt;

</content>
        
        <author>
            <name>Julie Lundgren og Tale Gjøvik</name>
            
        </author>
        
    </entry>
    
    <entry>
        <title>Hello world!</title>
        <link rel="alternate" type="text/html" href="https://tech.finn.no2016/11/01/working-for-another-sch-company/"/>
        <updated>2016-11-01T10:17:32+00:00</updated>
        <id>https://tech.finn.no/2016/11/01/working-for-another-sch-company</id>
        <content type="html">&lt;p&gt;As some of you may know, FINN is owned by &lt;a href=&quot;http://www.schibsted.com/&quot;&gt;Schibsted&lt;/a&gt;. What may not be that familiar, is that Schibsted has online classified solutions in several markets around the world, including &lt;a href=&quot;http://www.blocket.se&quot;&gt;Blocket&lt;/a&gt; in Sweden, &lt;a href=&quot;http://www.leboncoin.fr&quot;&gt;Le Bon Coin&lt;/a&gt; in France and &lt;a href=&quot;http://www.vibbo.es&quot;&gt;Vibbo&lt;/a&gt; in Spain. Schibsted has several hundreds of developers distributed around the globe, and there is a strong need and will to coordinate and cooperate across geographical boundaries.&lt;/p&gt;

&lt;h2 id=&quot;the-need-to-cooperate&quot;&gt;The need to cooperate&lt;/h2&gt;
&lt;p&gt;Reinventing the wheel is generally a bad idea. With that in mind, Schibsted has a strong focus on building global components that can be useful for all the companies within Schibsted. However, it is not always easy to understand the needs and challenges across national borders. There are cultural differences and different companies have reached different maturity levels. FINN has been around for years, and is one of the more mature classified sites within Schibsted. In an effort to help building global components, Schibsted has set up an international hub in Barcelona and developers from the local companies are encouraged to come to Barcelona to help out, share knowledge and build a common platform. Several developers from FINN have taken up the challenge and spent some months in beautiful and vibrant Barcelona. Here are Øyvind’s impressions.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/2016-06-28-working-for-another-sch-company/20160628_160134.jpg&quot; alt=&quot;developers_in_bcn&quot; title=&quot;happy campers&quot; /&gt;
&lt;em&gt;Anders, Øyvind and Per Jørgen. Three happy FINN-campers in Barcelona&lt;/em&gt;&lt;/p&gt;

&lt;h2 id=&quot;øyvind-goes-messaging&quot;&gt;Øyvind goes messaging&lt;/h2&gt;
&lt;p&gt;Øyvind has worked as a developer for FINN for 9 years, but when he got the opportunity to move to Barcelona for 6 months, the decision was easy.&lt;/p&gt;

&lt;p&gt;- The decision was really easy. There was little risk involved and I thought it would be an excellent opportunity to experience Barcelona. I love the city and the international atmosphere at the office. I think we are around 25 nationalities working here.&lt;/p&gt;

&lt;p&gt;- Tell us about the work you have been doing in Barcelona&lt;/p&gt;

&lt;p&gt;- I work as a developer here, in a team that developes a global messaging-component to be used by Schibsted-owned classified-sites around the world. When I arrived in January, we had integrated 6 global sites and the platform handled hundreds of thousands messages/day. Today, we have even more sites using our platform. It’s worth mentioning that the messaging-platform I’m working on, originally was developed by FINN. In 2014, however, it was decided that this platform was to be promoted to become a global component, so the responsibility was moved from Oslo to the global components team in Barcelona. The developers in Barcelona inherited the FINN code-base and developed it from being a FINN-only solution to a full-fledged multitenant solution. It’s not easy to inherit code like that, but I feel the ownership has been transferred now. The Barcelona-team owns the product and is soon going to integrate with FINN as well. We’ll replace FINN’s messaging platform with the global component-version, but that is not an easy task. FINN needs to preserve it’s messaging history, and that means we have to import 150 million messages! It’s a big job for our Cassandra-nodes.&lt;/p&gt;

&lt;p&gt;- What are the technical differences compared to working in FINN?&lt;/p&gt;

&lt;p&gt;- We use &lt;a href=&quot;http://aws.amazon.com/&quot;&gt;Amazon Web Services&lt;/a&gt; and it is really great to see how easy it is to scale up and set things into production. The infrastructure team here in Barcelona has done an amazing job and I feel it is really at the bleeding edge of technology working in this environment.&lt;/p&gt;

&lt;p&gt;- You are soon returning to Norway. What will you miss from your stay in Barcelona?&lt;/p&gt;

&lt;p&gt;- The people! It has been great working here, in this including atmosphere.&lt;/p&gt;

&lt;p&gt;- Learned any lessons?&lt;/p&gt;

&lt;p&gt;- When working in FINN, it’s easy to forget that you are part of a bigger company. I see that clearly now, the global perspective. It’s inspiring to know that I am a part of a global company.&lt;/p&gt;

&lt;h2 id=&quot;epilogue&quot;&gt;Epilogue&lt;/h2&gt;
&lt;p&gt;Øyvind spent 6 months in Barcelona and came home with lots of new knowledge, new friends and a Messi-shirt. He contributed in building global components and believes FC Barcelona will win Champions League again this season. The migration of the FINN-messaging platform went off without a hitch and we are still &lt;a href=&quot;https://finn.no/apply-here&quot;&gt;looking for great developers&lt;/a&gt;!&lt;/p&gt;

</content>
        
        <author>
            <name>Per Jørgen Walstrøm</name>
            
        </author>
        
    </entry>
    
    <entry>
        <title>Åpen studentkveld hjemme hos FINN</title>
        <link rel="alternate" type="text/html" href="https://tech.finn.no2016/10/18/open-student-night/"/>
        <updated>2016-10-18T00:00:00+00:00</updated>
        <id>https://tech.finn.no/2016/10/18/open-student-night</id>
        <content type="html">&lt;p&gt;FINN.no skal ha en åpen studentkveld der vi forteller litt om hvordan vi jobber med fokus på teknologi, produkt og brukeropplevelse.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Dette vil skje den 3. november fra klokken 16:00 – 20:00, her i våre lokaler i Grensen 5-7 i Oslo.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Hvem kan melde seg på?&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Alle som studerer ved en relevant linje på et universitet eller en høyskole&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Våre tre mål for kvelden&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Dele kunnskap om hvordan vi jobber&lt;/li&gt;
  &lt;li&gt;Fortelle litt om hva vi ser etter i kandidater, og hjelpe studentene med å få et innblikk i hvordan reisen er fra student til jobb. Enten det er i FINN eller andre steder.&lt;/li&gt;
  &lt;li&gt;Vi ønsker selvfølgelig også å profilere FINN Teknologi og Brukeropplevelse som et spennende sted å jobbe, uten at dette er et rekrutteringsarrangement&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;I disse timene kommer vi til å gi dere et innblikk i hvordan det er å jobbe med Norges mest besøkte nettside.&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Vi viser hvordan vi setter brukeren i fokus og jobber med å skape brukeropplevelser som begeistrer.&lt;/li&gt;
  &lt;li&gt;Hør om hvordan vi jobber i landets ledende interne teknologimiljø. Hvilke teknologier vi benytter, hvordan vi organiserer oss og hva vi ser etter når vi ansetter folk i FINN.&lt;/li&gt;
  &lt;li&gt;Du vil også få tips om hvordan det er å gå fra studenttilværelsen til å bli en medarbeider i et profesjonelt selskap&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Som i alt vi gjør så skal vi også ha det litt gøy, vi bjudar på pizza og øl  :-)&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Hilsen alle oss i FINN&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Disse plassene står og venter på deg:
&lt;img src=&quot;/images/2016-02-15-pen-fagkveld-presentasjoner/IMG_7612.jpg&quot; alt=&quot;alt text&quot; title=&quot;Presentasjoner i resepsjonen.&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Takk til alle som kom for en hyggelig kveld. Her er presentasjonene.&lt;/p&gt;

&lt;h1 id=&quot;presentasjoner&quot;&gt;Presentasjoner&lt;/h1&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;/images/2016-10-18-open-student-night/2016-11-03 Velkommen.pdf&quot;&gt;Velkommen&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;/images/2016-10-18-open-student-night/FINN.no-Små-og-hyppige-leveranser.pdf&quot;&gt;Små og hyppige Leveeranser!&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;/images/2016-10-18-open-student-night/fra-student-til-finn.pdf&quot;&gt;Fra Student til FINN&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;/images/2016-10-18-open-student-night/2016-11-03 Hva-ser-vi-etter-når-vi-rekrutterer.pptx&quot;&gt;Hva ser vi etter når vi rekrutterer?&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;/images/2016-10-18-open-student-night/Hvordan-skape.pptx&quot;&gt;Hvordan Skape&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</content>
        
        <author>
            <name>Nicolai Høge</name>
            
            <email></email>
            
        </author>
        
    </entry>
    
    <entry>
        <title>Browser statistics October 2016</title>
        <link rel="alternate" type="text/html" href="https://tech.finn.no2016/10/04/browser-statistics-october-2016/"/>
        <updated>2016-10-04T10:02:32+00:00</updated>
        <id>https://tech.finn.no/2016/10/04/browser-statistics-october-2016</id>
        <content type="html">&lt;p&gt;Last year, we &lt;a href=&quot;http://tech.finn.no/2015/06/25/browser-statistics-june-2015/&quot;&gt;presented the browser statistics of FINN.no as of June 2015&lt;/a&gt;. It’s time for an update!&lt;/p&gt;

&lt;h2 id=&quot;how-many-visitors-use-a-desktop-or-laptop&quot;&gt;How many visitors use a desktop or laptop?&lt;/h2&gt;

&lt;p&gt;Our first graph shows the share of our users using a mobile phone, tablet or a desktop computer to access FINN.no, regardless of whether they use our responsive web app (m.finn.no), or a native Android or iPhone app. 66% of our visits are now from a smartphone or a tablet. &lt;em&gt;The traditional desktop/laptop has a market share of 34%.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;/images/2016-10-04-browser-statistics-oct-2016/Visits per channel percent.png&quot;&gt;&lt;img class=&quot;center-block&quot; alt=&quot;Channel graph&quot; src=&quot;/images/2016-10-04-browser-statistics-oct-2016/Visits per channel percent.png&quot; /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If we exclude the native app users, which are obviously on either Android or iOS, what is the distribution between table, mobile and “other” (desktop) on our web site?&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;/images/2016-10-04-browser-statistics-oct-2016/Visits per channel percent ohne app.png&quot;&gt;&lt;img class=&quot;center-block&quot; alt=&quot;Channel graph without app&quot; src=&quot;/images/2016-10-04-browser-statistics-oct-2016/Visits per channel percent ohne app.png&quot; /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2 id=&quot;which-finnno-application-do-people-use&quot;&gt;Which FINN.no application do people use?&lt;/h2&gt;

&lt;p&gt;We have two major offerings of FINN.no - the responsive web (served from the domains m.finn.no and www.finn.no), and native apps for mobile devices. 24% of our visits are from our native apps, and 76% from our responsive web.&lt;/p&gt;

&lt;p&gt;Since we’re in the middle of a migration, some of our internal details leaks out in this graph; some parts of the finn.no traffic is served from the domain www.finn.no and the majority from the domain m.finn.no. They are supposed to be merged “real soon now”.&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;/images/2016-10-04-browser-statistics-oct-2016/Visits per application percent.png&quot;&gt;&lt;img class=&quot;center-block&quot; alt=&quot;Application graph&quot; src=&quot;/images/2016-10-04-browser-statistics-oct-2016/Visits per application percent.png&quot; /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2 id=&quot;browsers&quot;&gt;Browsers&lt;/h2&gt;

&lt;p&gt;First of all, let’s take a look at the numbers of the &lt;em&gt;browser vendors&lt;/em&gt;. The ranking is clear, Apple is the biggest, ahead of Google, Microsoft, Samsung and Mozilla. Opera is no longer in the top 5.&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;/images/2016-10-04-browser-statistics-oct-2016/browser-types.png&quot;&gt;&lt;img class=&quot;center-block&quot; alt=&quot;All providers&quot; src=&quot;/images/2016-10-04-browser-statistics-oct-2016/browser-types.png&quot; /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In case you wondered which browser is the biggest on the “desktop” here’s the trend. (Ignore the orange and blue triangle markers)&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;/images/2016-10-04-browser-statistics-oct-2016/desktop.png&quot;&gt;&lt;img class=&quot;center-block&quot; alt=&quot;Browsers, Windows&quot; src=&quot;/images/2016-10-04-browser-statistics-oct-2016/desktop.png&quot; /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The &lt;em&gt;complete browser statistics&lt;/em&gt;, across all applications and devices are as follows:&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;/images/2016-10-04-browser-statistics-oct-2016/browsers-all.png&quot;&gt;&lt;img class=&quot;center-block&quot; alt=&quot;All browsers&quot; src=&quot;/images/2016-10-04-browser-statistics-oct-2016/browsers-all.png&quot; /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It still seems like a good idea to make sure your website works well with Safari! The old giant Microsoft now has only the 5th spot with IE 11 (6.8%) and the 12th with Edge 13 (1.7%).&lt;/p&gt;

&lt;h2 id=&quot;mobile-details&quot;&gt;Mobile details&lt;/h2&gt;

&lt;p&gt;We got a request to give some more details about the mobile devices that uses FINN.no. Apple still has a strong position in Norway!&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;/images/2016-10-04-browser-statistics-oct-2016/mobile-devices.png&quot;&gt;&lt;img class=&quot;center-block&quot; alt=&quot;Mobile devices&quot; src=&quot;/images/2016-10-04-browser-statistics-oct-2016/mobile-devices.png&quot; /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The most popular browsers on mobile devices:&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;/images/2016-10-04-browser-statistics-oct-2016/browsers-mobile.png&quot;&gt;&lt;img class=&quot;center-block&quot; alt=&quot;All browsers&quot; src=&quot;/images/2016-10-04-browser-statistics-oct-2016/browsers-mobile.png&quot; /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Then the question is - what is the “Android Browser”? This is the break down of devices that has delivered traffic from the “Android Browser”.&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;/images/2016-10-04-browser-statistics-oct-2016/android-browser-devices.png&quot;&gt;&lt;img class=&quot;center-block&quot; alt=&quot;All browsers&quot; src=&quot;/images/2016-10-04-browser-statistics-oct-2016/android-browser-devices.png&quot; /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Similarily for the Samsung Browser:.&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;/images/2016-10-04-browser-statistics-oct-2016/samsung-browser-devices.png&quot;&gt;&lt;img class=&quot;center-block&quot; alt=&quot;All browsers&quot; src=&quot;/images/2016-10-04-browser-statistics-oct-2016/samsung-browser-devices.png&quot; /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And finally the trend on mobile browsers, by vendor:&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;/images/2016-10-04-browser-statistics-oct-2016/browsers-mobile-trend.png&quot;&gt;&lt;img class=&quot;center-block&quot; alt=&quot;All browsers&quot; src=&quot;/images/2016-10-04-browser-statistics-oct-2016/browsers-mobile-trend.png&quot; /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2 id=&quot;which-version-of-android-or-ios&quot;&gt;Which version of Android or iOS?&lt;/h2&gt;

&lt;p&gt;Our apps need to work well on several versions of Android and iOS. How fast are our users upgrading? This shows the distribution of operating systems among our apps users.&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;/images/2016-10-04-browser-statistics-oct-2016/android-app-version.png&quot;&gt;&lt;img class=&quot;center-block&quot; alt=&quot;Android version&quot; src=&quot;/images/2016-10-04-browser-statistics-oct-2016/android-app-version.png&quot; /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;/images/2016-10-04-browser-statistics-oct-2016/ios-app-version.png&quot;&gt;&lt;img class=&quot;center-block&quot; alt=&quot;iOS version&quot; src=&quot;/images/2016-10-04-browser-statistics-oct-2016/ios-app-version.png&quot; /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;/images/2016-10-04-browser-statistics-oct-2016/android-app-version-top15.png&quot;&gt;&lt;img class=&quot;center-block&quot; alt=&quot;Android version&quot; src=&quot;/images/2016-10-04-browser-statistics-oct-2016/android-app-version-top15.png&quot; /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;/images/2016-10-04-browser-statistics-oct-2016/ios-app-version-top15.png&quot;&gt;&lt;img class=&quot;center-block&quot; alt=&quot;iOS version&quot; src=&quot;/images/2016-10-04-browser-statistics-oct-2016/ios-app-version-top15.png&quot; /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Given that the user have an iPhone, which models are in use? Unfortunately, the data are quite spotty, but this graph might still be an indication of the truth.&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;/images/2016-10-04-browser-statistics-oct-2016/iphone-models.png&quot;&gt;&lt;img class=&quot;center-block&quot; alt=&quot;iOS version&quot; src=&quot;/images/2016-10-04-browser-statistics-oct-2016/iphone-models.png&quot; /&gt;&lt;/a&gt;&lt;/p&gt;

</content>
        
        <author>
            <name>Henning Spjelkavik</name>
            
        </author>
        
    </entry>
    
    <entry>
        <title>Getting down to business with Prometheus</title>
        <link rel="alternate" type="text/html" href="https://tech.finn.no2016/08/26/down-to-business-with-prometheus/"/>
        <updated>2016-08-26T13:00:00+00:00</updated>
        <id>https://tech.finn.no/2016/08/26/down-to-business-with-prometheus</id>
        <content type="html">&lt;p&gt;Having moved towards an architecture of microservices, FINN is already leveraging a number of technologies for identifying and dealing with service outages within this architectural style.
With each piece of functionality being comprised of a growing number of individual services, specialized tools are required for detection, analysis and mitigation of errors, and we are already using our fair share: Zipkin, Hystrix, Kibana, Grafana and Sensu to name some.&lt;/p&gt;

&lt;p&gt;When it comes to metrics and monitoring of time series data, FINN has traditionally employed an infrastructure based on StatsD and Graphite.
Recently, however, we opted to switch to &lt;a href=&quot;https://prometheus.io/&quot;&gt;Prometheus&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;img width=&quot;80&quot; src=&quot;/images/2016-08-18-down-to-business-with-prometheus/prometheus.png&quot; alt=&quot;Prometheus logo&quot; align=&quot;left&quot; style=&quot;padding-right: 10px&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Prometheus, which is heavily inspired by Google’s Borgmon monitoring system, was originally developed by SoundCloud as a reaction to scaling issues experienced with just StatsD and Graphite.
Taking into mind Adrian Cockcroft’s rule &lt;a href=&quot;http://www.slideshare.net/adriancockcroft/gluecon-monitoring-microservices-and-containers-a-challenge&quot;&gt;#4 of monitoring&lt;/a&gt;:&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;Monitoring systems need to be more available and scalable than the systems being monitored&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Prometheus stands out as a great choice for us. Not only is it directly supported by &lt;a href=&quot;http://kubernetes.io/&quot;&gt;Kubernetes&lt;/a&gt;, the future container management platform of choice here at FINN, it has been engineered from the ground up to deal with issues of scale and stability.
In this new area of monitoring microservices, &lt;a href=&quot;https://www.vividcortex.com/blog/2015/11/05/nobody-loves-graphite-anymore/&quot;&gt;Graphite seems to be losing ground&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The switch to Prometheus has been rapidly implemented by all our autonomous service teams. Along with monitoring of the “traditional” application metrics like latency and memory usage, special care has also been taken to ensure implementation of adequate monitoring of &lt;em&gt;business metrics&lt;/em&gt;.&lt;/p&gt;

&lt;h1 id=&quot;business-metrics-monitoring&quot;&gt;Business metrics monitoring&lt;/h1&gt;

&lt;p&gt;The implications of one failing or partly broken service can be hard to evaluate when you are dealing with a large number of services. Since a complex problem now can be broken up into units that are truly independent, all individual parts can continue to work fine separately, while the end result is just not working. Business metrics monitoring is a key tool to successfully discovering and mitigating problems in this world.&lt;/p&gt;

&lt;p&gt;Monitoring business metrics means monitoring the core performance indicators associated with the services you provide. These are the primary features of the business which will potentially suffer if any operational or functional part of the system is not performing.
Turnbull describes business metrics as&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;the next layer up from application metrics […] Business metrics might include the number of new users/customers, number of sales, sales by value or location, or anything else that helps measure the state of a business&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;(from Turnbull, James: &lt;a href=&quot;https://www.artofmonitoring.com/&quot;&gt;The Art of Monitoring&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;Other examples of such metrics are orders per second at Amazon, or &lt;a href=&quot;http://techblog.netflix.com/2015/02/sps-pulse-of-netflix-streaming.html&quot;&gt;stream starts per second at Netflix&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;My team at FINN, &lt;a href=&quot;http://www.finn.no/smajobber/&quot;&gt;FINN småjobber&lt;/a&gt; (FINN småjobber is Norways leading marketplace for matching labor and demand for help with everyday tasks such as cleaning, moving, delivery and handyman work - and more!) has recently finished implementing metrics monitoring with Prometheus. The effort will require some tuning in the weeks to come in order to adjust thresholds for automatic alerts and so forth, but is already looking quite good.&lt;/p&gt;

&lt;p&gt;Here I have collected five tips related to metrics monitoring with Prometheus based on our recent experiences, hopefully you may find some of them helpful.&lt;/p&gt;

&lt;h2 id=&quot;counters-for-the-win&quot;&gt;Counters for the win&lt;/h2&gt;

&lt;p&gt;Counters are great. Unlike gauges, which can spike when you are not looking, counters has no loss of information between samples.
But learn the core rule of thumb when working with them:&lt;br /&gt;
&lt;em&gt;The only mathematical operations you can safely directly apply to a counter are rate, irate, increase, and resets. Anything else will cause you problems&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;Tip courtesy of &lt;a href=&quot;http://www.robustperception.io/rate-then-sum-never-sum-then-rate/&quot;&gt;Brian Brazil&lt;/a&gt;.&lt;/p&gt;

&lt;h2 id=&quot;remember-recording-rules&quot;&gt;Remember recording rules&lt;/h2&gt;

&lt;p&gt;Once your data are turned into an instant vector (like when aggregating with the &lt;em&gt;sum&lt;/em&gt; function), further application of functions requiring range vectors is naturally not possible. However, there are times when you want to continue transforming your queries, and business metrics monitoring is one area where this fast becomes a reality. Say, if the business metric is &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;number_of_purchases&lt;/code&gt;, you want to monitor that the total number of purchases is at a healthy level, across all your servers. Assuming counters of type &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;number_of_purchases{server=&quot;X&quot;}&lt;/code&gt;, a prometheus query for this is&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;sum(increase(number_of_purchases[10m]))
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;This yields a meaningful metric to reason about - the total number of new purchases the previous 10 minutes. 
To alert on deviances in this metric, functions like &lt;em&gt;deriv&lt;/em&gt;, &lt;em&gt;delta&lt;/em&gt; and &lt;em&gt;holt_winters&lt;/em&gt; may be useful, but these all require range vectors.
A solution is to record the data using a &lt;a href=&quot;https://prometheus.io/docs/querying/rules/&quot;&gt;recording rule&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Recording rules allow for creating new time series from user-configured expressions, and means you can now query on these as if they were just any other metric.&lt;/p&gt;

&lt;p&gt;-&lt;/p&gt;

&lt;p&gt;Recording rules are otherwise used to precompute computationally expensive expressions and save them as new time series, and are particularly useful when used in monitoring dashboards (which are often refreshed at frequent intervals).&lt;/p&gt;

&lt;h2 id=&quot;business-metric-monitoring-tip-1-use-time-to-deal-with-seasonality&quot;&gt;Business metric monitoring tip #1: Use time() to deal with seasonality&lt;/h2&gt;

&lt;p&gt;Business metrics, like &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;number_of_purchases&lt;/code&gt;, are sometimes varying a lot throughout the day, typically correlated with the traffic to your site, and often also showing trends related to day of week or other signs of seasonality. Functions like &lt;em&gt;holt_winters&lt;/em&gt; may help in alerting on such metrics, but sometimes this is not adequate, for example when the general volume is just too low.&lt;/p&gt;

&lt;p&gt;At such times, the &lt;em&gt;time&lt;/em&gt; function may come in handy. time() returns the number of &lt;em&gt;seconds&lt;/em&gt; since January 1, 1970, and as such may for example be used to determine the current hour of day:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;(time() / (60 * 60)) % 24
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;With this in hand, expressions can be built to incorporate special handling for chosen periods, for example to compensate for nighttime hours where traffic is low and when alerts on low volumes typically would be triggered.&lt;/p&gt;

&lt;p&gt;Adding the expression &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;+ (((time() / (60 * 60)) % 24) &amp;lt; bool 8) * 1000&lt;/code&gt; to a query would for example add 1000 to a metric for hours 00:00 to 08:00, which may then rule these periods out for any alerting thresholds which you may have set.&lt;/p&gt;

&lt;figure&gt;
    &lt;img src=&quot;/images/2016-08-18-down-to-business-with-prometheus/time_function.png&quot; alt=&quot;Time function used to represent hour of day&quot; /&gt;
    &lt;figcaption&gt;time() function for the current hour of day&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;(Depending on your alerting tool, the option to exclude time periods from your alerts may also be configurable in that tool. However, this will usually not remedy the status in any dashboards for the metric, something which using a time()-clause in the query accomplishes).&lt;/p&gt;

&lt;h2 id=&quot;business-metric-monitoring-tip-2-use-micro-metrics&quot;&gt;Business metric monitoring tip #2: Use micro-metrics&lt;/h2&gt;

&lt;p&gt;Not strictly related to Prometheus as a technology, you may often find that the volume of an interesting metric is so low that efficient monitoring and alerting is hard. Low volumes however, does not mean that monitoring is not useful, it merely means that it will take a longer time to find deviances in the metric in question. The problem is very much analogous to that of doing A/B testing on low-traffic websites (like explained &lt;a href=&quot;https://help.optimizely.com/Set_Up_Optimizely/Testing_on_low-traffic_websites&quot;&gt;here&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;A tip in this situation is to instead, or in addition to your key metrics, monitor correlated &lt;em&gt;micro-metrics&lt;/em&gt; (or &lt;em&gt;micro-conversions&lt;/em&gt; to use the terminology from A/B-testing).
Here, instead of monitoring the key metric &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;number_of_purchases&lt;/code&gt;, one would monitor related metrics like clicks on the “add to cart”-button or the number of product page views. The main point is that the higher the volume, the faster one can detect deviances in metrics. This is fundamentally due to the fact that statistical significance increases with a higher sample population, and a higher sample population is, of course, faster to achieve with high traffic.&lt;/p&gt;

&lt;h2 id=&quot;use-better-graphing-tools-to-effectively-tune-your-queries&quot;&gt;Use better graphing tools to effectively tune your queries&lt;/h2&gt;

&lt;p&gt;Ok, the prometheus GUI comes with some core graphing capabilities, but it really cannot compare to what is provided by a tool like &lt;a href=&quot;http://grafana.org/&quot;&gt;Grafana&lt;/a&gt;. In Grafana, which has support for Prometheus out-of-the-box, you can stack metrics, override time intervals, template variables and much, much more, all which enables more efficient testing and analysis of your queries.&lt;/p&gt;

&lt;hr /&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;

&lt;p&gt;Wrapping up, I would like to recommend the previously referenced blog by Brian Brazil at Robust Perception for more interesting Prometheus stuff: &lt;a href=&quot;http://www.robustperception.io/blog/&quot;&gt;http://www.robustperception.io/blog/&lt;/a&gt;, and of course the &lt;a href=&quot;https://prometheus.io/docs/introduction/overview/&quot;&gt;official docs&lt;/a&gt;, which also links to where you can get in touch with the &lt;a href=&quot;https://prometheus.io/community/&quot;&gt;community&lt;/a&gt;.&lt;/p&gt;
</content>
        
        <author>
            <name>Håvard Nesvold</name>
            
        </author>
        
    </entry>
    
    <entry>
        <title>Summer Project 2016: Customer Insight</title>
        <link rel="alternate" type="text/html" href="https://tech.finn.no2016/08/12/summer-project-2016/"/>
        <updated>2016-08-12T11:04:35+00:00</updated>
        <id>https://tech.finn.no/2016/08/12/summer-project-2016</id>
        <content type="html">&lt;p&gt;This year’s summer intern team consisted of five technology students from UiO and NTNU and one in-house design intern. We have released continuously throughout an eight week period, and have ended with a final solution which is available to the public.&lt;/p&gt;

&lt;p&gt;The problem presented for this year’s summer project was; &lt;em&gt;What can FINN offer professional vendors at Torget to motivate them to continue advertising as professional vendors.&lt;/em&gt; To help us get started, we had a few meetings with FINN’s innovation team to work out an ambition and a few ideas. Our ambition was to create value for professional vendors that use Torget as a market to run their business. To achieve this we aimed to provide better advertisement insight for vendors, and give them a better look into their general performance at Torget.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/2016-08-12-summer-project-2016/geo.png&quot; alt=&quot;Geography and Demography&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Our application consists of three main parts. The first presents geographical data about the ad’s visitors, show	n on a map of Norway. This is primarily shown as percentages on each of Norway’s five regions, with more specific information shown in the cat’s conversation box. The latter also changes when the user clicks around the map. The second part of the application shows demographic data about the customers, which includes age and gender. Here we hope to open a more insightful relationship between seller and customer, which might for example allow sellers to properly target their correct customer group when advertising new products.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/2016-08-12-summer-project-2016/ranking.png&quot; alt=&quot;Ranking&quot; /&gt;&lt;/p&gt;

&lt;p&gt;The last part of the application is a competitive ranking list that compares ads in the user’s category based on number of visits. This is shown with green text to the right of each ad in the list. The user also has the option to change which category they want to see the ranking for, in the blue menu. One of the main goals here is to give users an incentive towards comparing their advertisements to their competitors’, by letting them see what part of the market their customers prefer.&lt;/p&gt;

&lt;h3 id=&quot;implementing-the-application&quot;&gt;Implementing the application&lt;/h3&gt;
&lt;p&gt;The architecture we went with for the application can mostly be divided into two parts: One that collects statistical data, and one that presents that data to the customer. These meet in the middle through a PostgreSQL database.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/2016-08-12-summer-project-2016/architecture.png&quot; alt=&quot;Architecture&quot; /&gt;&lt;/p&gt;

&lt;p&gt;The collecting part of the architecture is made up of a Java application that listens to events using FINN’s messaging broker, Kafka. This is usually referred to as a consumer. In our situation the consumer receives a message every time a user clicks on a sales ad, so that we can continue to aggregate anonymous statistical data for that specific ad.&lt;/p&gt;

&lt;p&gt;The presenting part is mostly made up of a three layer hierarchy. At the bottom we have a Java REST-API that reads from the database, which is built using Spring Boot. The next layer is a Node.js application, which is responsible for responding to all customer facing requests. Upon receiving a visit this application performs an initial server-side render of the website, which is built using React. This also includes a primary API request, so as to be able to present a mostly complete website to the user on the first request.&lt;/p&gt;

&lt;p&gt;This has the positive side effect that the application works decently even in browsers with JavaScript disabled. Upon receiving the initial render of the website the browser client takes over, which we consider the topmost layer of the presentational hierarchy. From here we fire off more API requests, to retrieve the data which is needed to present the rest of the website. This is an important step in maintaining a low average response time, as the user will be able to use most of the application while waiting for the other API requests to finish.&lt;/p&gt;

&lt;p&gt;The geographical map and the statistical charts are made using &lt;a href=&quot;https://d3js.org/&quot;&gt;D3.js&lt;/a&gt;. The map itself is written using pure D3, while the charts make use of the React friendly wrapper &lt;a href=&quot;http://formidable.com/open-source/victory/&quot;&gt;Victory&lt;/a&gt;. The data that lays the foundation for the map is transformed from raw Kartverket data to a D3 friendly format, using &lt;a href=&quot;https://github.com/mbostock/topojson&quot;&gt;TopoJSON&lt;/a&gt;, a library made by the same author as D3. This allows us to build interactions with the map using regular D3 code, which we at the moment utilise to merge the Norwegian counties into larger regions, and to react to the user’s hover and click events.&lt;/p&gt;

&lt;p&gt;The front- and backend applications are deployed in Docker containers, and managed through FINN’s new Kubernetes cluster. This follows FINN Infrastructure’s new deployment strategy, and has made our meeting with pipelines and deployment work easy.&lt;/p&gt;

&lt;h3 id=&quot;the-designers-perspective&quot;&gt;The designers’ perspective&lt;/h3&gt;
&lt;p&gt;When creating new features, technology is obviously important, but making sure people can use it is just as essential. As the team’s designers, keeping the user in mind was our priority number one. We spent the summer sketching, prototyping, testing, and continuously iterating to make the final design as user friendly as possible. This required a close collaboration with the developers in the team and attention to detail.&lt;/p&gt;

&lt;p&gt;We started out with an idea workshop where we sketched out different layout suggestions. This worked as the basis for further development of the user interface. We then made clickable prototypes in Adobe Experience Design and Invision, which were used in both formal user tests in FINN’s usability lab and more spontaneous ones in the busy streets of Oslo. The purpose of these tests was to assess people’s understanding of the information presented to them on the screen, as well as general navigation and interaction with the interface. Based on this feedback we were able to improve the different features in the app and ended up with a result we’re very pleased with.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/2016-08-12-summer-project-2016/usertest.jpg&quot; alt=&quot;User Testing&quot; /&gt;&lt;/p&gt;

&lt;h3 id=&quot;final-thoughts---this-has-been-a-fun-summer&quot;&gt;Final thoughts - this has been a fun summer!&lt;/h3&gt;
&lt;p&gt;After eight weeks of working at FINN we’ve had the pleasure of being introduced to a wide variety of both people and technologies. Asking for help has never been an issue, and we’re pretty sure we’ve bothered most of the people daring enough to work here this summer. To be able to release a product and receive feedback from actual users is definitely an exciting experience, especially for such a large customer base as FINN’s.&lt;/p&gt;

&lt;p&gt;So long, and thanks for all the parrots.
&lt;img src=&quot;/images/2016-08-12-summer-project-2016/parrot.gif&quot; alt=&quot;parrot&quot; /&gt;&lt;/p&gt;
</content>
        
        <author>
            <name>Anne Cathrine Saarem</name>
            
        </author>
        
        <author>
            <name>Hanne Marie Trelease</name>
            
        </author>
        
        <author>
            <name>Ingrid Vestby Fredriksen</name>
            
        </author>
        
        <author>
            <name>Sebastian Søberg</name>
            
        </author>
        
        <author>
            <name>Esben Slaatto</name>
            
        </author>
        
        <author>
            <name>Martin Ek</name>
            
        </author>
        
    </entry>
    
    <entry>
        <title>Splitting up a storyboard in an Objective-C/Swift mixed legacy project</title>
        <link rel="alternate" type="text/html" href="https://tech.finn.no2016/06/22/splitting-up-a-storyboard/"/>
        <updated>2016-06-22T11:19:07+00:00</updated>
        <id>https://tech.finn.no/2016/06/22/splitting-up-a-storyboard</id>
        <content type="html">&lt;h2 id=&quot;splitting-up-a-storyboard-in-an-objective-cswift-mixed-legacy-project&quot;&gt;Splitting up a storyboard in an Objective-C/Swift mixed legacy project&lt;/h2&gt;

&lt;p&gt;&lt;em&gt;&lt;strong&gt;How do you go about splitting up your storyboard into multiple smaller storyboards? And why would you? In this article I’ll tell you why we wanted to do this, what kind of problems we encountered along the way, and how we solved them.&lt;/strong&gt;&lt;/em&gt;&lt;/p&gt;

&lt;h3 id=&quot;the-problem-large-storyboards-makes-ios-developers-into-sad-pandas&quot;&gt;The problem: Large storyboards makes iOS developers into sad pandas&lt;/h3&gt;

&lt;p&gt;As the FINN app has grown the last few years, so has our storyboard. Using storyboards means you get several useful features, and it can give you a visual conceptual overview of how your app works. I say “can”, because this is not necessarily the case. Also, using storyboard has some quirks that are not always appreciated when multiple developers are working on the same project. Any small change to a storyboard might mean several changes to the storyboard xml. In addition to this, XCode has an annoying tendency to recalculate coordinates for several storyboard items just because you opened the file! And yet another annoyance is that the larger your storyboard, the slower it is to work with. We usually waited at least 5 seconds just for the file to open, and actions were often laggy.&lt;/p&gt;

&lt;p&gt;So, the iOS team had long ago decided it was time to split it up, but that job is not necessarily easy. Or small. Or even fun. And the Jira task for it also stated “Beware of scope creep…”. Yeah, it crept.&lt;/p&gt;

&lt;h3 id=&quot;the-finn-app-some-history&quot;&gt;The FINN app: Some history&lt;/h3&gt;

&lt;p&gt;The current FINN app was launched in August 2013. During these three years, the app has of course grown a bit, and during the last year or so several new features have been added using Swift, and quite a few of the older Objective-C classes have been rewritten in Swift. However, the majority of the code base is still Objective-C. Interoperability between Swift and Objective-C is therefore crucial.&lt;/p&gt;

&lt;p&gt;A quick count of files says that we currently have 171 Swift files and 349 .m files in our project (not counting third-party code, of course). These files contain  &amp;gt;44,000 lines of code, comprised of 30,000 lines of Objective-C code and &amp;gt;14,000 lines of Swift code (excluding comments and whitespace).&lt;/p&gt;

&lt;h3 id=&quot;starting-out-small&quot;&gt;Starting out small&lt;/h3&gt;

&lt;p&gt;We decided to start small and extract the part of our app called “Min FINN” (My FINN) to its own storyboard first. This is a fairly autonomous, although not quite, part of the app. Most of the navigation within this part of the app is internal to that feature, but there are a few entry points into it from other parts of the app.&lt;/p&gt;

&lt;p&gt;Some of the navigation is done by segues, but there are multiple places that instantiates the scene’s viewcontroller via the storyboard directly. This, of course, means code like this is littered throughout the codebase:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-objectivec&quot; data-lang=&quot;objectivec&quot;&gt;&lt;span class=&quot;n&quot;&gt;FINSearchListViewController&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;resultViewController&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;storyboard&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;instantiateViewControllerWithIdentifier&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;@&quot;resultListViewController&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;];&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;There are 2 problems with this:&lt;/p&gt;
&lt;ol&gt;
  &lt;li&gt;It refers to “self.storyboard”, which means it will only work as long as the viewcontroller exists on the same storyboard as the current viewcontroller&lt;/li&gt;
  &lt;li&gt;It uses a hard-coded string to refer to the storyboard identifier “resultListViewController”, which is error-prone&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;We needed a better way.&lt;/p&gt;

&lt;p&gt;When we started out, this is how our MainStoryboard_iPhone looked like:
&lt;img src=&quot;/images/2016-06-17-splitting-up-a-storyboard/mainstoryboard.before.png&quot; alt=&quot;Main storyboard before split&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Yes, kinda messy and not necessarily super-manageable. And not very informative either, conceptually. So how do we go about splitting it up?&lt;/p&gt;

&lt;p&gt;With XCode 7, we got a new, nice feature - Refactor to storyboard… :&lt;br /&gt;
&lt;img src=&quot;/images/2016-06-17-splitting-up-a-storyboard/refactor.to.storyboard.png&quot; alt=&quot;Refactor to storyboard&quot; /&gt;&lt;/p&gt;

&lt;p&gt;You just select all the scenes you want to extract and then this refactor feature will both create a new storyboard for you, and it will wire up any connections between scenes in the old storyboard and the new storyboard. &lt;a href=&quot;http://stackoverflow.com/a/33691412/1485715&quot;&gt;This, however, doesn’t work&lt;/a&gt; if you’re supporting iOS 8 AND you’re using relationship segues (i.e. segues from a UITabBarController).&lt;/p&gt;

&lt;p&gt;Which is the case for us, of course. But, at least it gives us a handy shortcut for extracting the scenes into a new storyboard. We just deleted the resulting storyboard references. Besides, for non-relationship situations we would be on our own anyway.&lt;/p&gt;

&lt;h3 id=&quot;juggling-two-storyboards&quot;&gt;Juggling two storyboards&lt;/h3&gt;

&lt;p&gt;Ok, so now we had a new storyboard, with just the “Min FINN” scenes:
&lt;img src=&quot;/images/2016-06-17-splitting-up-a-storyboard/minfinn.storyboard.png&quot; alt=&quot;MinFINN storyboard&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Most of the “self.storyboard instantiateViewControllerWithIdentifier calls spread out around the code still work, but not all. For example, in some areas of the Min FINN storyboard, we open up the FINObjectViewController that is still on the MainStoryboard. And there are a couple of scenes on the Min FINN storyboard that are accessed from scenes on the main storyboard. How can we know at call site which storyboard a given scene is located on? There are many such invocations. And as we later down the line continue to split up the main storyboard, this will be even more fragmented. We needed a common place to handle this so that the call site didn’t need to know where a given scene is located.&lt;/p&gt;

&lt;h3 id=&quot;generating-common-code&quot;&gt;Generating common code&lt;/h3&gt;

&lt;p&gt;We started out by searching for already existing tools that could help us. We found several, among them &lt;a href=&quot;https://github.com/AliSoftware/SwiftGen&quot;&gt;Swiftgen&lt;/a&gt;, and tried them all.
Swiftgen is a very thorough and well-written tool for generating enums and structs that handle multiple storyboards. However, it supports only Swift and cannot be used for Objective-C, making it a no-go for us. It’s also quite elaborate with enums, structs, protocols and extensions, and outputs a fair amount of code.
Most other tools we found were either Swift-only or Objc-only, or they created only constants for the identifiers.&lt;/p&gt;

&lt;p&gt;But we very much liked the Swiftgen approach of creating functions that can be called directly, and that will return an instance of the correct class.&lt;/p&gt;

&lt;p&gt;So we decided to create our own generator. The first iteration was to create a Swift-class that did not rely on Swift enums that are unusable in Objc, and that could be called from both Swift and Objc. This seemed absolutely doable, and our first generated Swift-file had static functions like this:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot; data-lang=&quot;swift&quot;&gt;&lt;span class=&quot;kd&quot;&gt;static&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;func&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;instantiateWebViewController&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;FINWebViewController&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;storyboard&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;instantiateViewControllerWithIdentifier&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;MainStoryboardIdentifier&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;WebViewController&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;rawValue&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;as!&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;FINWebViewController&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;kd&quot;&gt;static&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;func&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;instantiateFrontPageSearchViewController&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;FrontPageSearchViewController&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;storyboard&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;instantiateViewControllerWithIdentifier&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;MainStoryboardIdentifier&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;FrontPageSearchViewController&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;rawValue&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;as!&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;FrontPageSearchViewController&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;h3 id=&quot;objective-c-compiler-complaining&quot;&gt;Objective-C compiler complaining&lt;/h3&gt;

&lt;p&gt;Looks pretty good, right? One would think so. Except, it doesn’t work in Objective-C. Why? The FINWebViewController is an Objective-C class, and therefore has that name on both sides of the table. Win! The FrontPageSearchController, however, is a Swift class, and therefore has the name “FINFrontPageSearchViewController” on the objc side of the table. Doh! When the instantiateFrontPageSearchViewController was called from objc it didn’t work, because the expected class was FINFrontPageSearchViewController and the returned class was FrontPageSearchViewController.&lt;/p&gt;

&lt;p&gt;After &lt;strong&gt;a lot&lt;/strong&gt; of trial and error (I can assure you that I’m sparing you a lot of painful details here), we finally gave in and decided to generate separate instantiator classes for objc and Swift. Win! Or was it?&lt;/p&gt;

&lt;p&gt;Not really. Since some of the Swift view controllers are used from both objc and Swift, we annotate them with the objc name:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot; data-lang=&quot;swift&quot;&gt;&lt;span class=&quot;kd&quot;&gt;@objc&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;FinUserAdListViewController&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;kd&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;UserAdListViewController&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;UIViewController&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;UICollectionViewDataSource&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;UICollectionVi&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;...&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;That’s all well and good, but since our Python script parses the storyboard files and extracts the storyboard identifiers and their respective custom class names (if any), we had class names &lt;strong&gt;with&lt;/strong&gt; the prefix (objc classes) and class names &lt;strong&gt;without&lt;/strong&gt; the prefix (Swift classes). This enabled us to check for this prefix while generating. When generating Objc code, we added the prefix to the Swift classname, and when generating Swift code, we left it as is. Remember, in the storyboard, the non-prefixed Swift class name was used.&lt;/p&gt;

&lt;p&gt;Now, this resulted in compiler warnings like this:
&lt;img src=&quot;/images/2016-06-17-splitting-up-a-storyboard/incompatible.pointer.types.png&quot; alt=&quot;Incompatible pointer types&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Double-you tee eff? Even though the UserAdListViewController class is annotated with the prefixed name, this doesn’t work. Since we practise zero-tolerance for warnings in our project, we needed to fix this. What about casting it to the class it’s supposed to return? Let’s give it a shot:
&lt;img src=&quot;/images/2016-06-17-splitting-up-a-storyboard/casting.to.prefixed.classname.png&quot; alt=&quot;Casting to prefixed classname&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Warning gone! Now it HAS to work! Yes?&lt;/p&gt;

&lt;p&gt;No. Although we’re instantiating a viewcontroller that is listed in the storyboard as a UserAdListViewController, which is annotated as a FINUserAdListViewController, this does not return a FINUserAdListViewController objc instance. What does it return? A UIViewController…&lt;/p&gt;

&lt;p&gt;(To be clear, the hurdles I’m listing here are probably less than half the hurdles and dead ends we’ve met. You would probably be quite bored if I had listed them all. And frankly, I don’t even remember them all anymore. Which is probably a good thing.)&lt;/p&gt;

&lt;p&gt;By this time, I was starting to feel somewhat inundated. And was repeatedly asking myself why I had picked &lt;strong&gt;this&lt;/strong&gt; task as my first task as a newcomer to the team.&lt;/p&gt;

&lt;p&gt;Allright, what if we define this view controller as “FINUserAdListViewController” (the objc annotated name) in the storyboard scene? Surely, that &lt;strong&gt;has&lt;/strong&gt; to work in Objc?&lt;/p&gt;

&lt;p&gt;Lo and behold, it did! Now Objective-C recognizes the class.&lt;/p&gt;

&lt;h3 id=&quot;swift-compiler-complaining&quot;&gt;Swift compiler complaining&lt;/h3&gt;

&lt;p&gt;Oh, wait. Now the generated Swift class doesn’t work?
&lt;img src=&quot;/images/2016-06-17-splitting-up-a-storyboard/use.of.undeclared.type.png&quot; alt=&quot;User of undeclared type&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Of course, in the Swift realm, there is no such class. It’s supposed to refer to UserAdListViewController. No problem, we’ll just &lt;strong&gt;remove&lt;/strong&gt; the prefix now when we’re generating the Swift code. But wait. The way we’d known whether a class was an Objc class or a Swift class was to check for this prefix, right? So how do we figure out whether a class found in the storyboard is a Swift class when they ALL have prefixes now?&lt;/p&gt;

&lt;p&gt;Ok. What does Objc classes have that Swift classes don’t, that is easily accessible from a script? Header files. So, we created a Python function that crawls through all the files in the project and collects all header-filenames in a Set. Then, when generating the Swift code, we test every class name (with an added “.h”) against this Set. Does the Set contain an entry with this name? Yes -&amp;gt; Objc class. No -&amp;gt; Swift class.&lt;/p&gt;

&lt;p&gt;Would it be possible to do that for Swift files instead? No, because there isn’t necessaritly a 1-1 correlation between Swift &lt;strong&gt;classes&lt;/strong&gt; and &lt;strong&gt;filenames&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Believe it or not: this was the last hurdle, and we now have generated code for Objc and Swift. For Objc, the generated code looks like this:
&lt;img src=&quot;/images/2016-06-17-splitting-up-a-storyboard/finstoryboards.instantiate.png&quot; alt=&quot;Objective-C function&quot; /&gt;&lt;/p&gt;

&lt;p&gt;And at call site:
&lt;img src=&quot;/images/2016-06-17-splitting-up-a-storyboard/finstoryboards.callsite.png&quot; alt=&quot;Objective-C call site&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Swift generated code:
&lt;img src=&quot;/images/2016-06-17-splitting-up-a-storyboard/storyboards.instantiate.png&quot; alt=&quot;Swift function&quot; /&gt;&lt;/p&gt;

&lt;p&gt;At call site:
&lt;img src=&quot;/images/2016-06-17-splitting-up-a-storyboard/storyboards.callsite.png&quot; alt=&quot;Swift call site&quot; /&gt;&lt;/p&gt;

&lt;p&gt;That’s about as simple as you can get it.&lt;/p&gt;

&lt;p&gt;Well, almost. You might have noticed we have a Swift enum for the storyboard identifier. We could, of course, skipped the enum and just used the identifier string directly in the instantiate method. We’re undecided on that for now, as we thought we might use the enums for something else. But we might end up removing the enums at a later point.&lt;/p&gt;

&lt;p&gt;As you can clearly see, this was not a straightforward task, and Swift/Objective-C in(ter)operability has some rough edges. So, to wrap this up I will list the steps you should follow, and at the end of this list there is a link to the Python script and a demo project if you’re facing the same challenges as we did. The script is unfortunately not optimized and generic so that you can plug and play, but it should be fairly easy to adjust it to your own needs. Feel free to generify it and create a pull request to enhance the script’s usefulness for others!&lt;/p&gt;

&lt;h3 id=&quot;setting-up-your-project-for-generation&quot;&gt;Setting up your project for generation&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;1 -&lt;/strong&gt; Choose a small, reasonably autonomous part of your app that will get the first separate storyboard.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2 -&lt;/strong&gt; Select the necessary scenes, go to Editor -&amp;gt; Refactor to Storyboard…&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3 -&lt;/strong&gt; Name your new storyboard. If you’re not using relationship segues, the generated storyboard references should work well for you, and you can leave them!&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4 -&lt;/strong&gt; Create a Run script under Buile Phases that will trigger the Python script (follow the guide in the &lt;a href=&quot;https://github.com/mariusw/MWStoryboardScenes&quot; title=&quot;MWStoryboardScenes&quot;&gt;GitHub project&lt;/a&gt; for this).&lt;/p&gt;

&lt;p&gt;As you can see in the demo project, the MWStoryboardScenes.py file should be placed in your project somewhere. In our projects, we have a Scripts folder for these things, and this is not added to the XCode project (but it is handled by Git as a part of the project, of course).&lt;/p&gt;

&lt;p&gt;Now, this script needs to run every time the project is built, &lt;strong&gt;before&lt;/strong&gt; building source files. For a guide on how to set this up, take a look at the GitHub repository readme. You also need to set up paths to your storyboards.&lt;/p&gt;

&lt;p&gt;When you’ve run this script for the first time, you have the functions that are necessary to instantiate your view controllers.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;5 -&lt;/strong&gt; Find all places where view controllers are instantiated via the storyboard (and not via segues) and change these invocations so that they use the generated storyboard functions.&lt;/p&gt;

&lt;p&gt;As mentioned, a more thorough explanation on how to use the generator is provided in the README of the &lt;a href=&quot;https://github.com/mariusw/MWStoryboardScenes&quot; title=&quot;MWStoryboardScenes&quot;&gt;GitHub project&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Good luck with splitting your storyboard :-)&lt;/p&gt;
</content>
        
        <author>
            <name>Marius Waldal</name>
            
        </author>
        
    </entry>
    
    <entry>
        <title>How to Make Your Colleagues Think Accessibility</title>
        <link rel="alternate" type="text/html" href="https://tech.finn.no2016/06/13/how-to-make-your-colleagues-think-accessibility/"/>
        <updated>2016-06-13T11:19:07+00:00</updated>
        <id>https://tech.finn.no/2016/06/13/how-to-make-your-colleagues-think-accessibility</id>
        <content type="html">&lt;figure&gt;
  &lt;img src=&quot;/images/2014-11-19-workshop-with-blindfolded-lunch/in-transport.jpeg&quot; alt=&quot;Employees walking around with black painted goggles.&quot; /&gt;
  &lt;figcaption class=&quot;b1&quot;&gt;What to do when your colleagues don't make accessible products.&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;When developing a website which affects the life of almost every person in Norway, accessibility should be a main priority. Norwegians  who are not able to use FINN.no are unable to find a home without assistance. Accessibility considerations are too often an afterthought, and this can have profound effects.&lt;/p&gt;

&lt;p&gt;In the Spring of 2014, a small group of employees got together to find out how to make our colleagues care more about accessibility. After two years, the measures we think work best are workshops, user tests, and automatic validation.&lt;/p&gt;

&lt;h2&gt;Workshops&lt;/h2&gt;

&lt;figure&gt;
  &lt;img src=&quot;/images/2016-06-13-how-to-make-your-colleagues-think-accessibility/workshop.jpg&quot; alt=&quot;Sticky notes on the wall on a workshop.&quot; /&gt;
  &lt;figcaption class=&quot;b1&quot;&gt;On a workshop we reveal accessibility issues and discuss how to solve them.&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;At our workshops we demonstrate how FINN.no works for people with disabilities before we let the team members test it the same way themselves. People find this a lot more inspiring than memorising WCAG 2.0 recommendations and running automatic validations locally.&lt;/p&gt;

&lt;h3&gt;How to test&lt;/h3&gt;

&lt;h4&gt;Scaling Pages&lt;/h4&gt;
&lt;p&gt;The simplest thing to test for is page scalability, both for fonts and for the entire page. Most desktop browsers make it possible to scale both fonts and page no matter how poorly coded the pages are, but on mobile phones it’s still quite common to lock page size to screen size. It must be possible to pinch zoom every page on mobile phones.&lt;/p&gt;

&lt;h4&gt;The Straw Test&lt;/h4&gt;
&lt;p&gt;Another simple method is to pretend that you are looking at the page through a straw. This simulates how the page will appear for partially sighted people who either have a limited field of vision or need to magnify the page to read it. This reveals relevant elements spaced too far apart in a way that will make it difficult to navigate the page.&lt;/p&gt;

&lt;h4&gt;Keyboard Navigation&lt;/h4&gt;
&lt;p&gt;Most pages are designed and coded with mouse click and touch screens in mind, and we easily forget about people with broken arms, motor disabilities, or other reasons for not being able to use a mouse. By navigating the page with only a keyboard, we test that focus on links and buttons appears in a logical order.&lt;/p&gt;

&lt;h4&gt;High Contrast&lt;/h4&gt;
&lt;p&gt;Many partially sighted people are sensitive to bright light or struggle to see the contrasts on a page. They often invert the colors on the screen or switch to high contrast mode. Check that your pages are still readable in these modes. If you are using sprites or in other ways put relevant information in a background image, you may notice that this disappears on computers running Windows.&lt;/p&gt;

&lt;h4&gt;Screen Reader&lt;/h4&gt;
&lt;p&gt;Blind people depend on a screen reader to use a computer. Most cell phones have a screen reader built in by default. On Apple devices this is called VoiceOver and on Android it’s called TalkBack. You can turn this on in the accessibility settings. It takes some practice to use a screen reader, but when you handle it, you are likely to uncover a huge number of accessibility flaws.&lt;/p&gt;

&lt;figure&gt;
  &lt;img src=&quot;/images/2016-06-13-how-to-make-your-colleagues-think-accessibility/strawtest.jpg&quot; alt=&quot;Man looking at the screen though a straw to do the straw test.&quot; /&gt;
  &lt;figcaption class=&quot;b1&quot;&gt;Devopers actually think accessibility testing is fun (but you don't really have to use a straw).&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;h2&gt;User Tests&lt;/h2&gt;
&lt;p&gt;By testing our site during the workshops, we uncover the most obvious bugs. However, we cannot know how it actually feels for users with disabilities to use our site. That’s why we run user tests with blind and partially sighted users. We have people who run user tests at FINN, but because we don’t know what it’s like to be blind, we don’t always understand why they react as they do. Therefore, we need some external expertise to assist us. We are cooperating with Blindeforbundet (The National Association of Blind and Partially sighted, NABP) who run these tests for us.&lt;/p&gt;

&lt;p&gt;Designers, developers, and product managers watch the tests, which are also videotaped for further analysis. After the tests, NABP hands over the videotapes with a commented report and recommended improvements.&lt;/p&gt;

&lt;h2&gt;Automatic Validation&lt;/h2&gt;
&lt;p&gt;Our next step is to include WCAG validation as a part of our automated tests. There are several tools offering this, including Pa11y and Tenon.io. We are likely to use Pa11y in our first effort, because it’s free.&lt;/p&gt;

&lt;p&gt;Automatic validation gives an overview over detectable WCAG violations, and may work as a KPI for accessibility awareness or even break builds with new violations. If all developers and designers pay attention to this, we hopefully will stop deploying code with new WCAG violations.&lt;/p&gt;

&lt;p&gt;Read more about &lt;a href=&quot;http://pa11y.org/&quot; target=&quot;_blank&quot;&gt;Pa11y&lt;/a&gt; and &lt;a href=&quot;https://tenon.io/&quot; target=&quot;_blank&quot;&gt;Tenon.io&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;Is It Working?&lt;/h2&gt;
&lt;p&gt;Accessibility used to be looked on as some kind of geeky pedantry for something that otherwise works fine. This attitude has changed over the past few years, and people take complaints about accessibility more seriously. In an internal speech I once mentioned that many of our pages weren’t scaleable on mobile. The next day, three teams had fixed that. A blind user complained that our iPhone app didn’t work with VoiceOver. This was fixed before the next release (several hours later). Another user complained that our banner ads caused epileptic seizure. This resulted in a huge debate on our forums, but sadly we still haven’t found a perfect way to use banners in a way that make both users and advertisers happy.&lt;/p&gt;

&lt;figure&gt;
  &lt;img src=&quot;/images/2016-06-13-how-to-make-your-colleagues-think-accessibility/metatag.png&quot; alt=&quot;Viewport meta tag with a red line accross 'maximum-scale=1, user-scaleable=no'.&quot; /&gt;
  &lt;figcaption class=&quot;b1&quot;&gt;Do you have &quot;maximum-scale=1, user-scaleable=no&quot; in your viewport metatag? Delete it! It took three of our teams less than a day to make their pages scaleable.&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;Whatever we do, the most effective way to get our accessibility flaws fixed is feedback from the users. We are not perfect yet, and we don’t always find accessible solutions that make everyone satisfied. Knowing that a user is unable to use FINN.no is far more convincing to our managers than being told that they have a WCAG violation. So please, if you have disabilities that make FINN.no hard to use, contact our service desk. It takes only one complaint to put your problem on our agenda.&lt;/p&gt;

</content>
        
        <author>
            <name>Tom Widerøe</name>
            
        </author>
        
    </entry>
    
    <entry>
        <title>Åpen fagkveld, presentasjoner</title>
        <link rel="alternate" type="text/html" href="https://tech.finn.no2016/02/15/pen-fagkveld-presentasjoner/"/>
        <updated>2016-02-15T22:04:17+00:00</updated>
        <id>https://tech.finn.no/2016/02/15/pen-fagkveld-presentasjoner</id>
        <content type="html">&lt;p&gt;&lt;img src=&quot;/images/2016-02-15-pen-fagkveld-presentasjoner/IMG_0715.jpg&quot; alt=&quot;alt text&quot; title=&quot;Sondre Gravir åpner fagkvelden&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Onsdag 10. februar så hadde vi 140 personer på besøk hos FINN. Vi fortale litt om hvordan vi jobber i FINN og serverte pølser og øl. Nedenfor kan du finne presentasjonene fra fagkvelden.&lt;/p&gt;

&lt;h2 id=&quot;programmet&quot;&gt;Programmet&lt;/h2&gt;

&lt;table&gt;
&lt;tr&gt;
&lt;td&gt;15:15 - 15:30&lt;/td&gt;
&lt;td&gt;Velkommen til FINN v/ adm.dir Sondre Gravir&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;15:30 – 16:10&lt;/td&gt;
&lt;td&gt;Brukerorientering og datadrevet produktutvikling&lt;br /&gt;
&lt;a href=&quot;https://schibsted.box.com/s/cjunftgwwl7u7gohfp8uzegyim7jcrj0&quot;&gt;Hvordan involvere brukeren?&lt;/a&gt; (Dag Olav)&lt;br /&gt;
&lt;a href=&quot;https://schibsted.box.com/s/7h2arlm72rss63z1ycji77u28op9zjb8&quot;&gt;Brukerinvolvering + data = suksess&lt;/a&gt; (Marthe)
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td width=&quot;20%&quot;&gt;16:10 – 16:45&lt;/td&gt;
&lt;td&gt;Pølser og øl&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td width=&quot;20%&quot;&gt;16:45 – 19:00&lt;/td&gt;
&lt;td&gt;To gjennomkjøringer av temasporene&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td width=&quot;20%&quot;&gt;19:00 – 21:00&lt;/td&gt;
&lt;td&gt;Mingling med øl og snacks&lt;/td&gt;
&lt;/tr&gt;
&lt;/table&gt;

&lt;p&gt;&lt;img src=&quot;/images/2016-02-15-pen-fagkveld-presentasjoner/IMG_0752.jpg&quot; alt=&quot;alt text&quot; title=&quot;Marthe fortelle om brukerorientering.&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Presentasjonene var delt inn i tre spor.&lt;/p&gt;

&lt;h3 id=&quot;temasporene&quot;&gt;Temasporene&lt;/h3&gt;
&lt;table&gt;
&lt;tr&gt;
&lt;td width=&quot;33%&quot;&gt;&lt;strong&gt;Spor 1:&lt;/strong&gt;&lt;br /&gt; Teknologi - kultur og organisering&lt;/td&gt;
&lt;td width=&quot;33%&quot;&gt;&lt;strong&gt;Spor 2:&lt;/strong&gt;&lt;br /&gt; Produktutvikling&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Spor 3:&lt;/strong&gt;&lt;br /&gt; Innovasjon og intraprenørskap&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;a href=&quot;https://schibsted.box.com/s/y09k3aiye8ruma9ftmy6leoil0hhoww3&quot;&gt;Why &amp;amp; How we do DevOps in FINN&lt;/a&gt; (Jo)&lt;br /&gt;
&lt;a href=&quot;https://schibsted.box.com/s/8tyutyfzdb2ib3loeh8vfg6litoj1cen&quot;&gt;Slik reorganiserte vi 100 utviklere&lt;/a&gt; (Nicolai)
&lt;/td&gt;
&lt;td&gt;
&lt;a href=&quot;https://schibsted.box.com/s/pyr9bvywglcrkm50tm5y85ugmu2it8h5&quot;&gt;Brukerorientert, målstyrt og iterativt&lt;/a&gt; (Bjørn Henrik &amp;amp; Bente Mari)&lt;br /&gt;
&lt;a href=&quot;https://schibsted.box.com/s/px5fsmk7uoh0esfhl84n426ob8rvj0zb&quot;&gt;Eksperimentering på skjema&lt;/a&gt; (Per Gunnar) &lt;br /&gt;
&lt;a href=&quot;https://schibsted.box.com/s/be6gezi7ctwtuxw6ohgrnmazzpgcjio1&quot;&gt;Brukeropplevelse i FINN&lt;/a&gt; (Kaija)
&lt;/td&gt;
&lt;td&gt;
&lt;a href=&quot;https://schibsted.box.com/s/ge9623npgzy4496wpuykkb47wsnrijmg&quot;&gt;Innovasjon&lt;/a&gt; (Jens)
&lt;/td&gt;
&lt;/tr&gt;
&lt;/table&gt;

&lt;p&gt;&lt;img src=&quot;/images/2016-02-15-pen-fagkveld-presentasjoner/IMG_0738.jpg&quot; alt=&quot;alt text&quot; title=&quot;Dag Olav om produktutvikling.&quot; /&gt;
&lt;img src=&quot;/images/2016-02-15-pen-fagkveld-presentasjoner/IMG_7612.jpg&quot; alt=&quot;alt text&quot; title=&quot;Presentasjoner i resepsjonen.&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Lær mer om FINN.no og se bilder og videoer her:
&lt;a href=&quot;https://www.finn.no/job/employer/company/1&quot;&gt;&lt;img src=&quot;/images/2016-02-15-pen-fagkveld-presentasjoner/FollowFINN.png&quot; alt=&quot;Følg FINN.no&quot; /&gt;&lt;/a&gt;&lt;/p&gt;
</content>
        
        <author>
            <name>Jo Odland</name>
            
            <email></email>
            
        </author>
        
    </entry>
    
    <entry>
        <title>Åpen fagkveld hjemme hos FINN</title>
        <link rel="alternate" type="text/html" href="https://tech.finn.no2016/01/23/pen-fagkveld-hjemme-hos-finn/"/>
        <updated>2016-01-23T20:27:47+00:00</updated>
        <id>https://tech.finn.no/2016/01/23/pen-fagkveld-hjemme-hos-finn</id>
        <content type="html">&lt;p&gt;Velkommen til åpen fagkveld &lt;strong&gt;onsdag 10. februar, fra kl 15-20&lt;/strong&gt; hjemme hos FINN i Grensen 5-7.&lt;/p&gt;

&lt;p&gt;Link til påmelding: (Beklager men arrangementet er fullt.)&lt;/p&gt;

&lt;p&gt;Før jul kom vi opp med en idé om å invitere til en fagkveld der vi skulle dele av våre FINNske erfaringer. Vi bestemte oss for å sjekke interessen for denne ideen ved å sende ut en forespørsel i sosiale medier. Vi fikk nesten 100 svar! Det har gjort oss ganske sikre på at det er interesse for en fagkveld på FINN, og vi har satt sammen et program basert på tilbakemeldingene vi mottok.&lt;/p&gt;

&lt;h3 id=&quot;tilbakemeldinger&quot;&gt;Tilbakemeldinger&lt;/h3&gt;

&lt;p&gt;&lt;img src=&quot;/images/2016-01-23-pen-fagkveld-hjemme-hos-finn/fordeling.png&quot; alt=&quot;alt text&quot; title=&quot;Logo Title Text 1&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;program&quot;&gt;Program&lt;/h2&gt;

&lt;table&gt;
&lt;tr&gt;
&lt;td&gt;14:45&lt;/td&gt;
&lt;td&gt;Dørene åpner&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;15:15&lt;/td&gt;
&lt;td&gt;Velkommen til FINN v/ adm.dir Sondre Gravir&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;15:30 – 16:00&lt;/td&gt;
&lt;td&gt;Brukerorientering og datadrevet produktutvikling&lt;br /&gt;
Hør om hvordan vi jobber for å forstå brukernes behov. Vi vil blant annet presentere hvordan vi jobbet med lanseringen av den nye FINN-plattformen og hvordan vi jobber med brukerinnsikt på Torget.
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td width=&quot;20%&quot;&gt;16:00 – 16:30&lt;/td&gt;
&lt;td&gt;Matbit og drikke&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td width=&quot;20%&quot;&gt;16:45 – 17:45&lt;/td&gt;
&lt;td&gt;Første gjennomkjøring av temaspor&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td width=&quot;20%&quot;&gt;18:00 – 19:00&lt;/td&gt;
&lt;td&gt;Andre gjennomkjøring av temaspor&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td width=&quot;20%&quot;&gt;19:00 – 20:00&lt;/td&gt;
&lt;td&gt;Enda mer mat og drikke!&lt;/td&gt;
&lt;/tr&gt;
&lt;/table&gt;

&lt;p&gt;Alle som deltar kan velge to av tre spor. Hvert spor varer én time, deretter rullering og én time til. Temasporene blir en blanding av lyntaler og diskusjon.&lt;/p&gt;

&lt;p&gt;Link til påmelding: (Beklager men arrangementet er fullt.)&lt;/p&gt;

&lt;h3 id=&quot;temasporene&quot;&gt;Temasporene&lt;/h3&gt;
&lt;table&gt;
&lt;tr&gt;
&lt;td width=&quot;33%&quot;&gt;&lt;strong&gt;Spor 1:&lt;/strong&gt;&lt;br /&gt; Teknologi - kultur og organisering&lt;/td&gt;
&lt;td width=&quot;33%&quot;&gt;&lt;strong&gt;Spor 2:&lt;/strong&gt;&lt;br /&gt; Produktutvikling&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Spor 3:&lt;/strong&gt;&lt;br /&gt; Innovasjon og intraprenørskap&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Hør om hvordan vi jobber for å ha landets ledende teknologimiljø. Vi vil bla. presentere veien til DevOps og hvordan vi reorganiserte 100 utviklere, transparent og inkluderende!&lt;/td&gt;
&lt;td&gt;Produktutvikling i teori og praksis. Vi vil presentere hvordan vi jobber innsiktsdrevet og målstyrt med å utvikle produkter som begeistrer&lt;/td&gt;
&lt;td&gt;Hør innovasjonshistorien vår og hvordan vi jobber for å opprettholde en sterk innovasjonskultur. Vi vil presentere både hva vi har lykkes med og ikke lykkes med. Her er det takhøyde og stort rom for læring!&lt;/td&gt;
&lt;/tr&gt;
&lt;/table&gt;

&lt;p&gt;Link til påmelding: (Beklager men arrangementet er fullt.)&lt;/p&gt;

&lt;p&gt;Husk å merke hvilke to spor du ønsker å delta på, og om du blir igjen til mingling fra 19-20. Vi lover deg – det kommer til å bli en fin(n) kveld!&lt;/p&gt;
</content>
        
        <author>
            <name>Jo Odland</name>
            
            <email></email>
            
        </author>
        
    </entry>
    
    <entry>
        <title>Why Try - Java 8 lambdas and checked Exceptions</title>
        <link rel="alternate" type="text/html" href="https://tech.finn.no2016/01/20/why-try-java-8-functional-programming/"/>
        <updated>2016-01-20T07:13:00+00:00</updated>
        <id>https://tech.finn.no/2016/01/20/why-try-java-8-functional-programming</id>
        <content type="html">&lt;figure&gt;
  &lt;img src=&quot;/images/2016-01-20-why-try-java-8-functional-programming/thumbnail.png&quot; alt=&quot;lambdas dislike checked exceptions&quot; /&gt;
  &lt;figcaption&gt;Why won't this compile? (And why does riskyTask throw a PrinterException?!)&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;One does not have to use lambdas in Java 8 long before running into the obstacle of checked Exceptions. Because the PrinterException is checked, the compiler forces us to deal with it, even within a lambda:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-java&quot; data-lang=&quot;java&quot;&gt;&lt;span class=&quot;kd&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;demonstrate&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;nc&quot;&gt;IntStream&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ones&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;IntStream&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;generate&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;ones&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;map&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;i&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;try&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;riskyTask&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
        &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;catch&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;PrinterException&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;e&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;nc&quot;&gt;System&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;out&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;println&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;
                &lt;span class=&quot;s&quot;&gt;&quot;Your printer is out of ink or laser beams!&quot;&lt;/span&gt;
            &lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
        &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;});&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;kd&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;riskyTask&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;a&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;throws&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;PrinterException&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;But we don’t like to do this. We use lambdas to express intent in a concise and elegant fashion. The try/catch-brackets feels like noise.&lt;/p&gt;

&lt;p&gt;For this reason, FINN.no’s open source  &lt;a href=&quot;https://github.com/finn-no/lambda-companion&quot;&gt;lambda-companion&lt;/a&gt; project introduces a useful structure for using lambdas in a world with checked Exceptions : Try.&lt;/p&gt;

&lt;p&gt;A Try represents a computation which might fail, and is always represented as a Success or a Failure (but never both). The concept borrows from &lt;a href=&quot;http://www.scala-lang.org/api/current/index.html#scala.util.Try&quot;&gt;Scala’s Try&lt;/a&gt;, and shares several properties to other monadic functional structures :&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;Future (completable success or failure)&lt;/li&gt;
  &lt;li&gt;Optional (present or empty value)&lt;/li&gt;
  &lt;li&gt;Either (one of two values)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;FINN.no’s Try is right-biased, meaning that one can map and flatMap on a Try without having to add specific logic to handle a Try being a Failure; the computation will simply only take place if it is a Success.&lt;/p&gt;

&lt;p&gt;Using a Try we could refactor our riskyTask-example:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-java&quot; data-lang=&quot;java&quot;&gt;&lt;span class=&quot;kd&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;demonstrate&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;nc&quot;&gt;IntStream&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ones&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;IntStream&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;generate&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;nc&quot;&gt;Stream&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;Try&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;Integer&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;tryStream&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ones&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;mapToObj&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;riskyTask&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;tryStream&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;forEach&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;kd&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Try&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;Integer&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;riskyTask&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;a&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Success&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&amp;gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;kd&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;Try&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;Integer&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;t&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;nc&quot;&gt;Integer&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;defaultNumber&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;nc&quot;&gt;Integer&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;i&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;t&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;recover&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;success&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;success&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;failure&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;defaultNumber&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;nc&quot;&gt;System&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;out&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;This is contrived of course. And one should rarely accept a Try as a method argument. (And one should rarely generate an infinite stream of 2s and print them out.)&lt;/p&gt;

&lt;p&gt;Lets look at a slightly more realistic example. Here we want to validate an order using different services that can throw a checked Exception:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-java&quot; data-lang=&quot;java&quot;&gt;&lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;OrderProcessor&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;

    &lt;span class=&quot;nc&quot;&gt;CustomerLookupService&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;customerLookupService&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; 
            &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;CustomerLookupService&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;();&lt;/span&gt;
    &lt;span class=&quot;nc&quot;&gt;ProductLookupService&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;productLookupService&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; 
            &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;ProductLookupService&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;();&lt;/span&gt;
    &lt;span class=&quot;nc&quot;&gt;PaymentService&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;paymentService&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; 
            &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;PaymentService&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;();&lt;/span&gt;

    &lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;static&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;main&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;[]&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;args&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;OrderProcessor&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;().&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;validateOrder&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;14L&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;21L&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;kd&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Boolean&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;validateOrder&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;Long&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;customerId&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Long&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;productId&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Try&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;of&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;customerLookupService:&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;lookup&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;customerId&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
                &lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;flatMap&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;customerFound&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;
                        &lt;span class=&quot;nc&quot;&gt;Try&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;of&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;productLookupService:&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;lookup&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;productId&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;))&lt;/span&gt;
                &lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;flatMap&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;price&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;
                        &lt;span class=&quot;nc&quot;&gt;Try&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;of&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;paymentService:&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;pay&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;customerId&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;price&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;))&lt;/span&gt;
                &lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;recover&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;Function&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;identity&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(),&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;handleFailure&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;kd&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Boolean&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;handleFailure&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;Throwable&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;throwable&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;nc&quot;&gt;System&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;out&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;println&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;
                &lt;span class=&quot;s&quot;&gt;&quot;Could not process order because of &quot;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; 
                        &lt;span class=&quot;n&quot;&gt;throwable&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;getCause&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;());&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Boolean&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;FALSE&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;kd&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;CustomerLookupService&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;nc&quot;&gt;Boolean&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;lookup&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;Long&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;customerId&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;throws&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Exception&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt; 
        &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Boolean&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;TRUE&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt; 
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;kd&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;ProductLookupService&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;nc&quot;&gt;Long&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;lookup&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;Long&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;productId&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;throws&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Exception&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt; 
        &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;50L&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt; 
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;kd&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;PaymentService&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;nc&quot;&gt;Boolean&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;pay&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;Long&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;customerId&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Long&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;price&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;throws&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Exception&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt; 
        &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Boolean&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;TRUE&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;In this example we combine services to determine if a Order is valid or not. We use Try.flatMap() because this guarantees that the provided lambda will only be run if the proceeding Try is a Success. If any of the 3 service calls result in a Failure, the flatMapping call-chain will simply roll the Failure forward. The recover-method takes two lambdas: One to be executed in the case of Success, and one to be executed in the case of Failure.&lt;/p&gt;

&lt;p&gt;You can read more about Try, Either, StreamableOptional and more in FINN.no’s open sourced lambda-companion project available at &lt;a href=&quot;https://github.com/finn-no/lambda-companion&quot;&gt;github&lt;/a&gt; and for use in your project through the central maven repository.&lt;/p&gt;

&lt;p&gt;Happy flatMapping, and may all your Tries be Successes!&lt;/p&gt;
</content>
        
        <author>
            <name>Sjur Millidahl</name>
            
            <email></email>
            
        </author>
        
    </entry>
    
    <entry>
        <title>The great big Schibsted programming language survey 2015</title>
        <link rel="alternate" type="text/html" href="https://tech.finn.no2015/11/21/the-great-big-schibsted-programming-language-survey-2015/"/>
        <updated>2015-11-21T21:28:28+00:00</updated>
        <id>https://tech.finn.no/2015/11/21/the-great-big-schibsted-programming-language-survey-2015</id>
        <content type="html">&lt;p&gt;During September, I collected answers on a survey about programming languages from people who work in one of the many companies that make up the Schibsted family. The survey is neither requested, sanctioned, approved or even suggested by anyone in management, in any Schibsted company. It is entirely my own personal pet-project, because I’m interested in these things.&lt;/p&gt;

&lt;p&gt;The survey opens with the following introduction:&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;In 2012, there was a discussion about technology choices, and
particulary programming languages to use in FINN. As always in such
discussions, claims like “It’s impossible to hire people who know
language X, so we need to always use Java” or “We can’t expect
programmers to learn a new language just because we think language Y
would be a better fit” were put forward.&lt;/p&gt;

  &lt;p&gt;Personally, I’ve never liked those arguments, because they contradict
what I think defines a “good programmer”, and I’m optimistically
hoping that we only hire “good programmers”. Also, instead of
opinions, beliefs, rumours and hearsay, let’s be data driven in these
discussions! So I decided I wanted to put it to the test, and created
a survey and posted it for all the company to see.&lt;/p&gt;

  &lt;p&gt;The results from that survey were blogged about here:
&lt;a href=&quot;/2012/09/04/leaving-the-tower-of-babel/&quot;&gt;http://tech.finn.no/2012/09/04/leaving-the-tower-of-babel/&lt;/a&gt;&lt;/p&gt;

  &lt;p&gt;Now, almost three years later, a lot of changes are happening. FINN
is becoming more close with it’s Schibsted brethren, Schibsted itself
is turning towards a more technological outlook, and I felt it was
time to get an updated view, and why not involve all of Schibsted
while we’re at it.&lt;/p&gt;

  &lt;p&gt;So, “The great big Schibsted programming language survey 2015” was
created, and hopefully it will make the rounds in all of Schibsted
and gather some interesting, fun, surprising and (with a bit of luck)
useful insights.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2 id=&quot;about-the-responses&quot;&gt;About the responses&lt;/h2&gt;

&lt;p&gt;&lt;img src=&quot;/images/the-great-big-schibsted-programming-language-survey-2015/responses_by_company.png&quot; alt=&quot;responses&quot; class=&quot;expandable-image expandable-image-small&quot; /&gt;&lt;/p&gt;

&lt;p&gt;309 people responded to the survey, which is more than many of my co-workers thought would respond.&lt;/p&gt;

&lt;p&gt;Over 50% of respondents are in their thirties, and if we look at the ages from 26 to 40, we cover over 75% of respondents. On gender, it’s even worse, with a whopping 93.5% of respondents admitting to being male. We also seem to shun inexperienced people, with 42% having 10 years of experience or more, and only 2.3% of respondents fresh from school.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/the-great-big-schibsted-programming-language-survey-2015/age_chart.png&quot; alt=&quot;age&quot; class=&quot;expandable-image expandable-image-small&quot; /&gt;&lt;/p&gt;

&lt;p&gt;It seems we have some work to do with regards to diversity.&lt;/p&gt;

&lt;p&gt;According to Arena (The Schibsted-wide intranet), there are just under 125 companies in the Schibsted familiy. In those companies, 9355 people are employed.&lt;/p&gt;

&lt;p&gt;That means somewhere between 20% and 30% response rate in the target group, which isn’t too bad.&lt;/p&gt;

&lt;p&gt;Unfortunately, our results are not representative. FINN is by no means the largest company, but supplied the most answers with 80 respondents. The second largest group was the various incarnations of Schibsted Product &amp;amp; Technology (SPT), which supplied 53 responses if we combine them all. Those two supplied over a third of all responses. Third place belongs to Schibsted Tech Polska (16 responses), which beat Wilhaben (15), Le Bon Coin (13), Blocket (12) and Aftonbladet (10) to close out the top 7.&lt;/p&gt;

&lt;p&gt;This probably means that the results are more a description of FINN and SPT than a description of Schibsted in general.&lt;/p&gt;

&lt;p&gt;Those 309 people listed 103 different companies, which is about 20 less than the total number of companies. That sounds great! Except I’m fairly certain that’s not what I have. Trying to normalize the data entered here, fixing variations in spelling and other issues I’ll get back to in a minute, I reduced it to 45 companies, one of which was Schibsted itself.&lt;/p&gt;

&lt;p&gt;Those issues are companies that don’t exist (atleast according to Arena)! I’ve tried my best to guess which company was really intended, but might have made mistakes here and there. In some cases, I had no idea which company to use instead, so I just left it as is, even if the company is not listed as part of Schibsted. Some examples: Schibsted Tech Madrid, SPT - Madrid, SCM Spain, SCM Central, Schibsted Norge Tech, Plan3 and Schibsted ATE. None of these were listed anywhere, but I’ve tried my best to place them some logical place.&lt;/p&gt;

&lt;p&gt;Another problem was SPT aka Schibsted Products &amp;amp; Technology. SPT is techically four companies, SPT Norway, SPT Sweden, SPT Spain and SPT UK. Most people in SPT simply answered SPT, but they estimated as little as 20 people working in SPT, and as high as 250. I’ve disregarded their disagreement, and combined them all to just SPT.&lt;/p&gt;

&lt;p&gt;Two people said they work in Schibsted, but they had wildly differing opinions about how many people work in Schibsted. One said 10000 total, 1500 technical, the other said 1000 total, 200 technical.&lt;/p&gt;

&lt;p&gt;Disregarding the problems here, let’s look at the numbers entered. Using the averages for each company and summing up, we arrive at roughly 4500 people working in those 47 companies. For technical positions, the sum of averages is roughly 1600. Since we have responses from about half the companies, that fits nicely with the total being just under 10000.&lt;/p&gt;

&lt;h2 id=&quot;languages-in-regular-use&quot;&gt;Languages in regular use&lt;/h2&gt;

&lt;p&gt;&lt;img src=&quot;/images/the-great-big-schibsted-programming-language-survey-2015/current-work.png&quot; alt=&quot;current_work&quot; class=&quot;expandable-image expandable-image-small&quot; /&gt;&lt;/p&gt;

&lt;p&gt;The most commonly used language, if you can call it that, is Bash. 52.1% of respondents use Bash regularly. Close behind, at 50.2% is JavaScript. Neither of these are surprising, considering the business we’re in. Java clocks in at 49.2%, and from there it’s quite a drop to Python at 28.2% and PHP at 24.9%. C manages 18.1%, Ruby and Scala are both used by 17.8%, and Groovy (11.7%) and Go (10.7%) conclude the top 10.&lt;/p&gt;

&lt;p&gt;Of the previously mentioned top 7 companies with regards to number of responses, Le Bon Coin and Blocket are the only two companies that are not predominantly Java shops. Those two have a more wide array of languages in common use, with high numbers for multiple languages. When we combine those two companies, 88% use C, 80% uses PHP, 72% uses Bash and sh, 64% uses Python and only 56% uses Java. Ruby, R are popular in Le Bon Coin, while Blockets own template language and JavaScript are popular in Blocket.&lt;/p&gt;

&lt;p&gt;Singling out FINN and SPT in a similar way, it’s a different story. Java is used by 76.7% in these companies, JavaScript and Bash/sh are around the same, at 48.1% and 46.6% respectively. Scala is popular with a share of 33.8% in the two companies. Next in line is Python at 24%, Groovy and R both at 21.8%. Ruby is used by 18%, and a group of PHP developers in SPT brings PHP into the top 10 with 6%.&lt;/p&gt;

&lt;h2 id=&quot;what-kind-of-developers-are-we&quot;&gt;What kind of developers are we?&lt;/h2&gt;

&lt;p&gt;The most popular self-description is “a backend developer” (124), followed closely by the “full stack developer” (82). There are slightly more devops people (29) than “frontend developers” (25). Of course, the most interesting fact about this question, is all the various roles I failed to think of. Over 40 other descriptions were entered in the “Other” box, some of them even by more than one person! Most notably the 10 or 11 data scientists (depending on how you interpret descriptions). Mobile developer or App developer is another one that should have been one of the choices.&lt;/p&gt;

&lt;h2 id=&quot;the-languages-we-know&quot;&gt;The languages we know&lt;/h2&gt;

&lt;p&gt;&lt;img src=&quot;/images/the-great-big-schibsted-programming-language-survey-2015/languages-you-know.png&quot; alt=&quot;languages-you-know&quot; class=&quot;expandable-image expandable-image-small&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Only 13 people do not know any Java at all, the remaining 296 respondents knows enough to tick the box. This makes Java the most known language, but it is closely followed by C and JavaScript. 268 respondents know C, while 263 know JavaScript. If ever we wanted to use &lt;a href=&quot;https://github.com/avleen/bashttpd&quot;&gt;bashttpd&lt;/a&gt; for anything, hardly anyone would need to be retrained, with 234 people already knowning enough bash to get around.&lt;/p&gt;

&lt;p&gt;PHP and Python follow, at 212 and 204 respectively. Personally I’m pleased to see so many knowing Python, and can only blame childhood mistakes for so many people knowing PHP. Dropping below 200 respondents we find R at 188 and C++ at 185. Ruby and Perl close out the top 10 languages, with 164 and 146. Hopefully all these people have learned Perl as a defence mechanism: Know thy enemy and all that…&lt;/p&gt;

&lt;p&gt;Of the more hip languages on the JVM, Scala comes out on top, with 117 people claiming to know it, while Groovy at 94 and Clojure at 53 haven’t convinced quite as many people that they are useful to know. Clojure isn’t even in the top 20.&lt;/p&gt;

&lt;p&gt;Outside the JVM, the top three contenders are all in the Microsoft family: C# (106), Basic (104) and Visual Basic (103). From far out in left field comes AWK at 98 respondents, while Pascal, the old favourite of so many, are known by only 86 people. Go has a sizeable following, at 81, narrowly beating Objective-C at 77. To close out the top 20, Haskell lays claim to the twentieth spot, with 61 people knowing what the heck Monads are.&lt;/p&gt;

&lt;p&gt;Moving into the twenties, CoffeeScript ties with Clojure and Prolog at 53, while Common Lisp has managed to stick in the head of 47 people. Lua is known by 42, while Erlang and Scheme are tied at 31. Microsoft hasn’t quite managed to get their PowerShell into as many heads as Bash, but 30 people are willing to admit knowing it. Swift (29) and Tcl (27) close out the top 30.&lt;/p&gt;

&lt;p&gt;Fortran (26) and PostScript (21) are starting to fall out of fashion, and we can point at 20 people working in various norwegian companies who probably got their education at the University of Oslo, starting with Simula as their first language. The story behind the single non-norwegian Simula-proficient person at Aftonbladet would be interesting to hear I imagine (or is it simply a norwegian who has moved to Sweden?).&lt;/p&gt;

&lt;p&gt;Other notable mentions are 19 people who know Smalltalk, 11 people knows D, a whopping 10 people have managed to learn Brainfuck, while 9 people know Rust and F# (not the same 9 though). The tail is quite long, and old heroes like Forth (8), OCaml (8), Standard ML (4), Delphi (2), Modula2 (2) and Ada (1) are still hanging on. There’s even a handful of people who knows some dialect of assembler.&lt;/p&gt;

&lt;h2 id=&quot;what-can-we-use&quot;&gt;What can we use?&lt;/h2&gt;

&lt;p&gt;Let’s take a close look at which languages some of the larger companies (in terms of number of respondents) could and should use.&lt;/p&gt;

&lt;p&gt;When starting their next project, SPT could choose Java, C, Python or JavaScript, and send less than a third of their people back to school. Allowing half the company to re-train, they could select from Scala, C++ or PHP, while Ruby, Perl, Groovy or C# would require sending close to two thirds of the company to school. Other popular choices are Go and Clojure, but that would require around 80% of the company to hit the books, so maybe not just yet.&lt;/p&gt;

&lt;p&gt;Blocket is fortunate enough to have four languages known by all respondents: Java, C, Python and PHP. If they want to use JavaScript, C++ or Perl, they are still quite prepared, while Ruby and Go haven’t quite the same following. If they want to be on the JVM, but not use Java, Groovy is their only choice, but they would still need to train 11 out of 12 people.&lt;/p&gt;

&lt;p&gt;At FINN, some people would be surprised to see that we are better prepared for a switch to C or Ruby than we are prepared for a switch to Scala. With 48 people knowing Scala, and 42 knowing Groovy, any of those two would be doable. We’d be stuck quite quickly if we go for Go, with only 16 of 80 knowing anything about it, and Clojure isn’t doing much better, at 26 out of 80. Half the company already knows C++, Python or PHP, so we could use those, while Perl, Pascal and Basic requires training more than half the company. Last year we had both a Clojure course (again) and a Haskell course for those interested, and 26 and 24 people managed to pick those up enough to click the right boxes.&lt;/p&gt;

&lt;p&gt;Le Bon Coin have a story similar to Blocket. They can hardly go wrong, with most of the company knowlegeable in Java, JavaScript, PHP, C and Python. A few don’t know Ruby or C++, while Go and Perl are only known by half the respondents. Groovy is the top alternate JVM language, but with only 2 out of 13, it’s not really a good choice. Scala and Clojure aren’t known at all, so functional programming on the JVM clearly isn’t a “thing” at LBC.&lt;/p&gt;

&lt;p&gt;Schibsted Tech Polska delivers to a number of other companies, so they need to know a bit of everything. This is reflected in their knowledge, with Java, C and JavaScript being known by almost all. Python and C++ are known by half the company, while a third of the company is able to hold their own in Groovy, Scala, Clojure, Ruby and PHP. a few of them even know Perl.&lt;/p&gt;

&lt;p&gt;Willhaben in Austria also know a few languages, with Java, C, JavaScript and C++ being known by almost all. PHP and Python is known by two thirds of the company, with Perl and Ruby edging onto the “known by half” list. Groovy, Scala just missed the 50% mark, along with Haskell.&lt;/p&gt;

&lt;h2 id=&quot;sql-and-its-many-variations&quot;&gt;SQL and its many variations&lt;/h2&gt;

&lt;p&gt;&lt;img src=&quot;/images/the-great-big-schibsted-programming-language-survey-2015/sql-dialects-you-know.png&quot; alt=&quot;sql-you-know&quot; class=&quot;expandable-image expandable-image-small&quot; /&gt;&lt;/p&gt;

&lt;p&gt;When talking about databases, it’s easy to think SQL is just “one thing”, and if you know SQL, you can work with any database. And it’s somewhat true, but each database has it’s idiosyncrasies. This becomes especially clear when writing stored procedures, where basic SQL is lacking in control structures. So which database should we be using, really?&lt;/p&gt;

&lt;p&gt;PL/pgSQL, the dialect used in PostgreSQL, is known by 193 respondents, so PostgreSQL seems like a good choice. SQL/PSM, which is used by MySQL and a few others, is known by 159 people, is a good second choice.&lt;/p&gt;

&lt;p&gt;Of the large commercial databases, Oracle is clear out in front, known by 143 people, while runner up T-SQL used by both Microsoft SQL Server and Sybase is at 106. Any other contenders in the fight can just pack it in and go to bed. Fifth place goes to SQL PL, used by IBM DB2, known by only 32 respondents!&lt;/p&gt;

&lt;p&gt;PSQL, used in Interbase and Firebird, makes a surprise showing at 18, while PL/PSM, the less used dialect supported by PostgreSQL has 17 users. Of the remainders only Watcom-SQL, used in some versions of Sybase, manages double digits, at 10 respondents.&lt;/p&gt;

&lt;h2 id=&quot;so-how-good-are-we-at-what-we-do&quot;&gt;So, how good are we at what we do?&lt;/h2&gt;

&lt;p&gt;&lt;img src=&quot;/images/the-great-big-schibsted-programming-language-survey-2015/experts.png&quot; alt=&quot;experts&quot; class=&quot;expandable-image expandable-image-small&quot; /&gt;&lt;/p&gt;

&lt;p&gt;The results here are somewhat skewed by the fact that people will naturally be more skilled in the languages they use regulary, and the companies with the most number of respondents use only a few of the languages regulary. As a result, there are more Java-experts than any other kind of experts. 102 respondents consider themselves Java-experts. Java is also the only language where there are more experts than any of the other “ranks”.&lt;/p&gt;

&lt;div style=&quot;clear: both;&quot;&gt;
&lt;img src=&quot;/images/the-great-big-schibsted-programming-language-survey-2015/expert/all/Java.png&quot; title=&quot;Java&quot; alt=&quot;Java&quot; class=&quot;expandable-image expandable-image-small&quot; /&gt;
&lt;img src=&quot;/images/the-great-big-schibsted-programming-language-survey-2015/expert/all/JavaScript.png&quot; title=&quot;JavaScript&quot; alt=&quot;JavaScript&quot; class=&quot;expandable-image expandable-image-small&quot; /&gt;
&lt;img src=&quot;/images/the-great-big-schibsted-programming-language-survey-2015/expert/all/PHP.png&quot; title=&quot;PHP&quot; alt=&quot;PHP&quot; class=&quot;expandable-image expandable-image-small&quot; /&gt;
&lt;/div&gt;
&lt;div style=&quot;clear: both;&quot; /&gt;

&lt;p&gt;Other languages we feel we know well are JavaScript and PHP. Just under 50 people consider themselves experts in those languages. Next is Python, with 22 experts. Objective-C and Scala have 11 experts each, and there are 7 Groovy-experts and 6 Ruby-experts in all of Schibsted.&lt;/p&gt;

&lt;p&gt;Languages we don’t really know are C#, Clojure, CoffeeScript, Go, Groovy, Objective-C, Scala and Swift. In fact, Clojure is also the only language where nobody considers them selves experts.&lt;/p&gt;

&lt;p&gt;Most people consider themselves above average when it comes to JavaScript (205 respondents scored 3 or higher), while only 15 don’t know it at all. Similary, 255 people consider themselves above average in Java, and only 13 don’t know it at all. Of the three most used languages, Bash is the only one where the skills are concentrated below average. 223 people scored themselves at 3 or less for Bash knowledge, of which 18 don’t know it at all. At the other end, a single person considers themselves a Bash-expert.&lt;/p&gt;

&lt;div style=&quot;clear: both;&quot;&gt;
&lt;img src=&quot;/images/the-great-big-schibsted-programming-language-survey-2015/expert/all/Python.png&quot; title=&quot;Python&quot; alt=&quot;Python&quot; class=&quot;expandable-image expandable-image-small&quot; /&gt;
&lt;img src=&quot;/images/the-great-big-schibsted-programming-language-survey-2015/expert/all/Objective-C.png&quot; title=&quot;Objective-C&quot; alt=&quot;Objective-C&quot; class=&quot;expandable-image expandable-image-small&quot; /&gt;
&lt;img src=&quot;/images/the-great-big-schibsted-programming-language-survey-2015/expert/all/Scala.png&quot; title=&quot;Scala&quot; alt=&quot;Scala&quot; class=&quot;expandable-image expandable-image-small&quot; /&gt;
&lt;/div&gt;
&lt;div style=&quot;clear: both;&quot; /&gt;

&lt;p&gt;The numbers are perhaps more useful if we separate out the people who don’t use a language in their current work, and then look at the distribution of skills in each group.&lt;/p&gt;

&lt;p&gt;I’ll pick a few interesting ones, graphs for all are available at the end.&lt;/p&gt;

&lt;p&gt;Java is almost symetrical among the “amateurs”. Most amateurs are about average, but of the rest, less and more knowledge is more or less equally distributed. This is in sharp contrast to the “pros”, where a large majority consider themselves experts. While it’s easy to think that in the group of “pros”, it will always be many experts, Java, Objective-C and PHP are the only languages where the experts are the largest group.&lt;/p&gt;

&lt;div style=&quot;clear: both;&quot;&gt;
&lt;img src=&quot;/images/the-great-big-schibsted-programming-language-survey-2015/expert/pro/Java.png&quot; title=&quot;Java&quot; alt=&quot;Java&quot; class=&quot;expandable-image expandable-image-small&quot; /&gt;
&lt;img src=&quot;/images/the-great-big-schibsted-programming-language-survey-2015/expert/pro/Objective-C.png&quot; title=&quot;Objective-C&quot; alt=&quot;Objective-C&quot; class=&quot;expandable-image expandable-image-small&quot; /&gt;
&lt;img src=&quot;/images/the-great-big-schibsted-programming-language-survey-2015/expert/pro/PHP.png&quot; title=&quot;PHP&quot; alt=&quot;PHP&quot; class=&quot;expandable-image expandable-image-small&quot; /&gt;
&lt;/div&gt;
&lt;div style=&quot;clear: both;&quot; /&gt;

&lt;p&gt;JavaScript is a similar story to Java, except there are fewer experts in JavaScript. It’s good to notice that everyone who works with JavaScript, actually do know the language, which was not the case with Java.&lt;/p&gt;

&lt;p&gt;Apart from Java and JavaScript, C and PHP are the only languages where a significant amount of amateurs consider themselves above average in skills. At the other end, Clojure, CoffeeScript, Go, Groovy, Objective-C, Scala and Swift are all examples of languages where a large portion of the amateurs consider themselves to either not know it at all, or know less than average.&lt;/p&gt;

&lt;p&gt;When comparing pro and amateurs, Objective-C really stands out. Most of the pros are experts, but among the amateurs it’s virtually an unknown language.&lt;/p&gt;

&lt;h2 id=&quot;what-next&quot;&gt;What next?&lt;/h2&gt;

&lt;p&gt;&lt;img src=&quot;/images/the-great-big-schibsted-programming-language-survey-2015/favorites.png&quot; alt=&quot;favorites&quot; class=&quot;expandable-image expandable-image-small&quot; /&gt;&lt;/p&gt;

&lt;p&gt;The most popular choice for the next project is Java. 55 people would go with Java if allowed to choose freely. 43 people prefer JavaScript. Go, Scala and Python are in the next group at 37, 36 and 31. 17 people would select Ruby, 12 would go with Swift or PHP, 11 thinks Clojure is a good choice, and 10 people would go with C.&lt;/p&gt;

&lt;p&gt;In this kind of survey, there are always some smartypants. Five people avoided the question with variations of “I’m not able to conceive of a project where the language I like the most is the perfect choice, so I’ll say it depends on some hypotetical project specification that I have no say over”. Other clever answers are Fortran, Visual Fox, and T-SQL. Some people prefer the simple over the complicated, and would go with languages usually reserved for small utility-scripts like AWK and Bash.&lt;/p&gt;

&lt;h2 id=&quot;self-improvement&quot;&gt;Self-improvement&lt;/h2&gt;

&lt;p&gt;&lt;img src=&quot;/images/the-great-big-schibsted-programming-language-survey-2015/motivations.png&quot; alt=&quot;motivations&quot; class=&quot;expandable-image expandable-image-small&quot; /&gt;&lt;/p&gt;

&lt;p&gt;73.6% of us will sit down and learn a new language just to learn something new. 65.1% will accept learning something new if they need it for work, while only 41.4% are willing to do the same for non-work.&lt;/p&gt;

&lt;p&gt;If we are so easy to motivate, what holds us back?&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/the-great-big-schibsted-programming-language-survey-2015/languages_to_know.png&quot; alt=&quot;languages&quot; class=&quot;expandable-image expandable-image-small&quot; /&gt;&lt;/p&gt;

&lt;p&gt;It could be the number of languages we think is “neccessary” to know. Knowing more than 6 languages is not neccessary in most peoples view, only 9.9% of us thinks it’s worth knowing 7 or more languages. 29% of us think 5 languages is good enough, while 6.8% thinks knowing one language well enough is all you need to be a good programmer.&lt;/p&gt;

&lt;p&gt;The majority seems to think between 3 and 5 languages should be our target. 70% think knowing 3, 4 or 5 languages defines a good programmer.&lt;/p&gt;

&lt;p&gt;Nobody thinks you’re any good if you know 9 languages, so if that’s you, you should set out to learn atleast one more as soon as possible. Or just forget one. :-D&lt;/p&gt;

&lt;h2 id=&quot;theres-probably-more-here&quot;&gt;There’s probably more here&lt;/h2&gt;

&lt;p&gt;I initially said I would blog about the results a couple weeks after the end of the survey. That ended up being a couple months instead. For that reason I have skipped looking at a few questions that could be interesting, but time consuming to look at.&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;How many languages do you know vs. How many languages does a good programmer know&lt;/li&gt;
  &lt;li&gt;Which language currently not in use in a company, is most popular in the company?&lt;/li&gt;
  &lt;li&gt;Does experience correlate with number of languages?&lt;/li&gt;
  &lt;li&gt;What else does experience correlate with, if anything?&lt;/li&gt;
  &lt;li&gt;Skill level in your favorite language&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I’m sure there are a number of other interesting things to look at, so I’m hoping someone else wants to take a look at the results and see what they find. The link is below. The first sheet is the raw, unedited responses. The second sheet is where I have tried to manually clean up some of the answers to get more sense out of the results. And all the other sheets are various views and tables I’ve used to make this post.&lt;/p&gt;

&lt;p&gt;Let me know if you blog about these results, and I’ll add a link here. Or just post in the comments below.&lt;/p&gt;

&lt;h3 id=&quot;resources&quot;&gt;Resources&lt;/h3&gt;

&lt;p&gt;&lt;a href=&quot;https://docs.google.com/spreadsheets/d/1naKVK5YnUb0suEFyNIZlN6p2ils1rUGVI0zdFn6jGgM/edit?usp=sharing&quot;&gt;The results in full&lt;/a&gt;
&lt;a href=&quot;/images/the-great-big-schibsted-programming-language-survey-2015/gen_graphs.py&quot;&gt;Script to generate graphs&lt;/a&gt;&lt;/p&gt;

&lt;h4 id=&quot;skill-levels-currently-using-the-language-at-work&quot;&gt;Skill levels, currently using the language at work&lt;/h4&gt;

&lt;p&gt;&lt;a href=&quot;/images/the-great-big-schibsted-programming-language-survey-2015/expert/pro/Bash.png&quot;&gt;Bash&lt;/a&gt;, &lt;a href=&quot;/images/the-great-big-schibsted-programming-language-survey-2015/expert/pro/C.png&quot;&gt;C&lt;/a&gt;, &lt;a href=&quot;/images/the-great-big-schibsted-programming-language-survey-2015/expert/pro/C%23.png&quot;&gt;C#&lt;/a&gt;, &lt;a href=&quot;/images/the-great-big-schibsted-programming-language-survey-2015/expert/pro/C++.png&quot;&gt;C++&lt;/a&gt;, &lt;a href=&quot;/images/the-great-big-schibsted-programming-language-survey-2015/expert/pro/Clojure.png&quot;&gt;Clojure&lt;/a&gt;, &lt;a href=&quot;/images/the-great-big-schibsted-programming-language-survey-2015/expert/pro/CoffeeScript.png&quot;&gt;CoffeeScript&lt;/a&gt;, &lt;a href=&quot;/images/the-great-big-schibsted-programming-language-survey-2015/expert/pro/Go.png&quot;&gt;Go&lt;/a&gt;, &lt;a href=&quot;/images/the-great-big-schibsted-programming-language-survey-2015/expert/pro/Groovy.png&quot;&gt;Groovy&lt;/a&gt;, &lt;a href=&quot;/images/the-great-big-schibsted-programming-language-survey-2015/expert/pro/Java.png&quot;&gt;Java&lt;/a&gt;, &lt;a href=&quot;/images/the-great-big-schibsted-programming-language-survey-2015/expert/pro/JavaScript.png&quot;&gt;JavaScript&lt;/a&gt;, &lt;a href=&quot;/images/the-great-big-schibsted-programming-language-survey-2015/expert/pro/Objective-C.png&quot;&gt;Objective-C&lt;/a&gt;, &lt;a href=&quot;/images/the-great-big-schibsted-programming-language-survey-2015/expert/pro/PHP.png&quot;&gt;PHP&lt;/a&gt;, &lt;a href=&quot;/images/the-great-big-schibsted-programming-language-survey-2015/expert/pro/Python.png&quot;&gt;Python&lt;/a&gt;, &lt;a href=&quot;/images/the-great-big-schibsted-programming-language-survey-2015/expert/pro/Ruby.png&quot;&gt;Ruby&lt;/a&gt;, &lt;a href=&quot;/images/the-great-big-schibsted-programming-language-survey-2015/expert/pro/Scala.png&quot;&gt;Scala&lt;/a&gt;, &lt;a href=&quot;/images/the-great-big-schibsted-programming-language-survey-2015/expert/pro/Swift.png&quot;&gt;Swift&lt;/a&gt;&lt;/p&gt;

&lt;h4 id=&quot;skill-levels-not-using-the-language-at-work&quot;&gt;Skill levels, not using the language at work&lt;/h4&gt;

&lt;p&gt;&lt;a href=&quot;/images/the-great-big-schibsted-programming-language-survey-2015/expert/amateur/Bash.png&quot;&gt;Bash&lt;/a&gt;, &lt;a href=&quot;/images/the-great-big-schibsted-programming-language-survey-2015/expert/amateur/C.png&quot;&gt;C&lt;/a&gt;, &lt;a href=&quot;/images/the-great-big-schibsted-programming-language-survey-2015/expert/amateur/C%23.png&quot;&gt;C#&lt;/a&gt;, &lt;a href=&quot;/images/the-great-big-schibsted-programming-language-survey-2015/expert/amateur/C++.png&quot;&gt;C++&lt;/a&gt;, &lt;a href=&quot;/images/the-great-big-schibsted-programming-language-survey-2015/expert/amateur/Clojure.png&quot;&gt;Clojure&lt;/a&gt;, &lt;a href=&quot;/images/the-great-big-schibsted-programming-language-survey-2015/expert/amateur/CoffeeScript.png&quot;&gt;CoffeeScript&lt;/a&gt;, &lt;a href=&quot;/images/the-great-big-schibsted-programming-language-survey-2015/expert/amateur/Go.png&quot;&gt;Go&lt;/a&gt;, &lt;a href=&quot;/images/the-great-big-schibsted-programming-language-survey-2015/expert/amateur/Groovy.png&quot;&gt;Groovy&lt;/a&gt;, &lt;a href=&quot;/images/the-great-big-schibsted-programming-language-survey-2015/expert/amateur/Java.png&quot;&gt;Java&lt;/a&gt;, &lt;a href=&quot;/images/the-great-big-schibsted-programming-language-survey-2015/expert/amateur/JavaScript.png&quot;&gt;JavaScript&lt;/a&gt;, &lt;a href=&quot;/images/the-great-big-schibsted-programming-language-survey-2015/expert/amateur/Objective-C.png&quot;&gt;Objective-C&lt;/a&gt;, &lt;a href=&quot;/images/the-great-big-schibsted-programming-language-survey-2015/expert/amateur/PHP.png&quot;&gt;PHP&lt;/a&gt;, &lt;a href=&quot;/images/the-great-big-schibsted-programming-language-survey-2015/expert/amateur/Python.png&quot;&gt;Python&lt;/a&gt;, &lt;a href=&quot;/images/the-great-big-schibsted-programming-language-survey-2015/expert/amateur/Ruby.png&quot;&gt;Ruby&lt;/a&gt;, &lt;a href=&quot;/images/the-great-big-schibsted-programming-language-survey-2015/expert/amateur/Scala.png&quot;&gt;Scala&lt;/a&gt;, &lt;a href=&quot;/images/the-great-big-schibsted-programming-language-survey-2015/expert/amateur/Swift.png&quot;&gt;Swift&lt;/a&gt;&lt;/p&gt;

&lt;h4 id=&quot;skill-levels-all-respondents&quot;&gt;Skill levels, all respondents&lt;/h4&gt;

&lt;p&gt;&lt;a href=&quot;/images/the-great-big-schibsted-programming-language-survey-2015/expert/all/Bash.png&quot;&gt;Bash&lt;/a&gt;, &lt;a href=&quot;/images/the-great-big-schibsted-programming-language-survey-2015/expert/all/C.png&quot;&gt;C&lt;/a&gt;, &lt;a href=&quot;/images/the-great-big-schibsted-programming-language-survey-2015/expert/all/C%23.png&quot;&gt;C#&lt;/a&gt;, &lt;a href=&quot;/images/the-great-big-schibsted-programming-language-survey-2015/expert/all/C++.png&quot;&gt;C++&lt;/a&gt;, &lt;a href=&quot;/images/the-great-big-schibsted-programming-language-survey-2015/expert/all/Clojure.png&quot;&gt;Clojure&lt;/a&gt;, &lt;a href=&quot;/images/the-great-big-schibsted-programming-language-survey-2015/expert/all/CoffeeScript.png&quot;&gt;CoffeeScript&lt;/a&gt;, &lt;a href=&quot;/images/the-great-big-schibsted-programming-language-survey-2015/expert/all/Go.png&quot;&gt;Go&lt;/a&gt;, &lt;a href=&quot;/images/the-great-big-schibsted-programming-language-survey-2015/expert/all/Groovy.png&quot;&gt;Groovy&lt;/a&gt;, &lt;a href=&quot;/images/the-great-big-schibsted-programming-language-survey-2015/expert/all/Java.png&quot;&gt;Java&lt;/a&gt;, &lt;a href=&quot;/images/the-great-big-schibsted-programming-language-survey-2015/expert/all/JavaScript.png&quot;&gt;JavaScript&lt;/a&gt;, &lt;a href=&quot;/images/the-great-big-schibsted-programming-language-survey-2015/expert/all/Objective-C.png&quot;&gt;Objective-C&lt;/a&gt;, &lt;a href=&quot;/images/the-great-big-schibsted-programming-language-survey-2015/expert/all/PHP.png&quot;&gt;PHP&lt;/a&gt;, &lt;a href=&quot;/images/the-great-big-schibsted-programming-language-survey-2015/expert/all/Python.png&quot;&gt;Python&lt;/a&gt;, &lt;a href=&quot;/images/the-great-big-schibsted-programming-language-survey-2015/expert/all/Ruby.png&quot;&gt;Ruby&lt;/a&gt;, &lt;a href=&quot;/images/the-great-big-schibsted-programming-language-survey-2015/expert/all/Scala.png&quot;&gt;Scala&lt;/a&gt;, &lt;a href=&quot;/images/the-great-big-schibsted-programming-language-survey-2015/expert/all/Swift.png&quot;&gt;Swift&lt;/a&gt;&lt;/p&gt;

</content>
        
        <author>
            <name>Morten Lied Johansen</name>
            
            <email></email>
            
        </author>
        
    </entry>
    
    <entry>
        <title>Javascript: from callback hell to heaven</title>
        <link rel="alternate" type="text/html" href="https://tech.finn.no2015/10/16/javascript-from-callback-hell-to-heaven/"/>
        <updated>2015-10-16T14:00:00+00:00</updated>
        <id>https://tech.finn.no/2015/10/16/javascript-from-callback-hell-to-heaven</id>
        <content type="html">&lt;p&gt;This blogpost explains how some nifty features in ES7 will make it easier to write asynchronous code, and how ES6 generators will pave the way for this.&lt;/p&gt;

&lt;h2 id=&quot;asyncawait&quot;&gt;Async/await&lt;/h2&gt;

&lt;p&gt;My main annoyance with javascript and Node has been the tedious asynchronous programming model of callbacks, leading to nested callbacks and the so called “callback hell” or “pyramid of doom”. Take for example the following code where we are creating a function that reads a file and parses it to JSON, and see how cumbersome it is to read and follow the code:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-javascript&quot; data-lang=&quot;javascript&quot;&gt;&lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;fs&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;require&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;fs&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;

&lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;readJSONFile&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;callback&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;nx&quot;&gt;fs&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;readFile&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;file.json&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;utf8&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;err&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;err&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;callback&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;err&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
        
        &lt;span class=&quot;kd&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;json&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;kd&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;parseError&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
        
        &lt;span class=&quot;k&quot;&gt;try&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;nx&quot;&gt;json&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;JSON&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;parse&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;catch&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;e&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;nx&quot;&gt;parseError&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;e&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
        
        &lt;span class=&quot;nx&quot;&gt;callback&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;parseError&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;json&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;};&lt;/span&gt;

&lt;span class=&quot;nx&quot;&gt;readJSONFile&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;((&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;err&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;json&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;err&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;c1&quot;&gt;//handle error&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;nx&quot;&gt;console&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;json&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;//continue program&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;This was slightly improved and simplified with ES6 &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise&quot;&gt;promises&lt;/a&gt;, however, as we can see in the following code, we are still stuck with nested .then-calls:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-javascript&quot; data-lang=&quot;javascript&quot;&gt;&lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;bluebird&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;require&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;bluebird&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;fs&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;bluebird&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;promisifyAll&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;require&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;fs&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;));&lt;/span&gt;

&lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;readJSONFile&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;fs&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;readFileAsync&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;file.json&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;utf8&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;then&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;data&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;JSON&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;parse&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;})&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;};&lt;/span&gt;

&lt;span class=&quot;nx&quot;&gt;readJSONFile&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;then&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;json&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;nx&quot;&gt;console&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;json&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
        &lt;span class=&quot;c1&quot;&gt;//continue program&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;})&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;catch&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;err&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;c1&quot;&gt;//handle error&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;})&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Callbacks and nested .then-statements will be a thing of the past with the upcoming version of Javascript (ES7). It will provide us with the keywords &lt;a href=&quot;https://tc39.github.io/ecmascript-asyncawait/&quot;&gt;async and await&lt;/a&gt;, which will enable us to write asynchronous code as we would have written it in an imperative synchronous way:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-javascript&quot; data-lang=&quot;javascript&quot;&gt;&lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;bluebird&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;require&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;bluebird&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;fs&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;bluebird&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;promisifyAll&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;require&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;fs&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;));&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;readJSONFile&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;data&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;fs&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;readFileAsync&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;file.json&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;utf8&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;JSON&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;parse&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;try&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;json&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;readJSONFile&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
        &lt;span class=&quot;nx&quot;&gt;console&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;json&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
        &lt;span class=&quot;c1&quot;&gt;//continue program&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;catch&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;err&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;c1&quot;&gt;//handle error&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;};&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;The await keyword can either take a promise or an async function, and pauses until the promise resolves, or the async function returns.  If the promise rejects or the async function throws an error, the error can be caught with a normal catch-statement.  This provides a significant improvement in readability, which likely leads to less bugs and a pleasure to write.&lt;/p&gt;

&lt;h2 id=&quot;generators&quot;&gt;Generators&lt;/h2&gt;

&lt;p&gt;Along with promises, ES6 brought &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Iterators_and_Generators&quot;&gt;generators&lt;/a&gt;, which allows a function to pause at a certain point (yield) and continue when requested with .next(), as illustrated in the following code:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-javascript&quot; data-lang=&quot;javascript&quot;&gt;&lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;hello&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;nx&quot;&gt;console&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;age&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;yield&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;nx&quot;&gt;console&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;age&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;helloGen&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;hello&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;Tor&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;nx&quot;&gt;helloGen&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;next&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;// =&amp;gt; Tor&lt;/span&gt;
&lt;span class=&quot;nx&quot;&gt;helloGen&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;next&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;37&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;// =&amp;gt; 37&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Generators are not very useful for normal application logic, but they provide the buildings blocks for some nifty features.&lt;/p&gt;

&lt;p&gt;First of all, ES7 async/await can be transpiled (&lt;a href=&quot;https://babeljs.io&quot;&gt;by Babel&lt;/a&gt;) to generators, so that javascript environments that supports generators (Chrome, Firefox and Node), also supports async/await.&lt;/p&gt;

&lt;p&gt;However, because Safari and Internet Explorer don’t support generators yet, it is to early to start using generators on the front-end, however, on the backend with Node, it can be safely used, because async/await keywords are transpiled to generators.&lt;/p&gt;

&lt;p&gt;A web framework in Node that is based around generators, and uses it for all it is worth, is &lt;a href=&quot;http://koajs.com&quot;&gt;Koa&lt;/a&gt;. They utilize generators to make the code behave similar to async/await:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-javascript&quot; data-lang=&quot;javascript&quot;&gt;&lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;Koa&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;require&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;koa&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;app&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;Koa&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
&lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;router&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;require&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;koa-router&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)();&lt;/span&gt;

&lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;getName&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;Promise&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;resolve&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;Tor&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;nx&quot;&gt;app&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;use&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;router&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;routes&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;());&lt;/span&gt;

&lt;span class=&quot;nx&quot;&gt;router&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;kd&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;/hello&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;name&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;yield&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;getName&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;body&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;Hello &lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;

&lt;span class=&quot;nx&quot;&gt;app&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;listen&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;3000&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;The &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/yield&quot;&gt;yield&lt;/a&gt; keyword behaves as await, and generator &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/function*&quot;&gt;function* &lt;/a&gt; behaves as async functions. The main motivation for this is to simplify writing middlewares, and avoid the mentioned callback hell. In the next version of Koa, the yield keyword and generator functions will be replaced with await and async.&lt;/p&gt;

&lt;h2 id=&quot;conclusion&quot;&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;So, ES7 provides a fantastic improvement in simplifying asynchronous code that makes code easier to read and write. These features can already be used on the backend with Node and web frameworks like Koa.&lt;/p&gt;

&lt;h2 id=&quot;credits&quot;&gt;Credits&lt;/h2&gt;
&lt;p&gt;Thanks to Sveinung Røsaker for improving the callback example.&lt;/p&gt;
</content>
        
        <author>
            <name>Tor Arne Kvaløy</name>
            
            <email></email>
            
        </author>
        
    </entry>
    
    <entry>
        <title>Setup nginx with HTTP/2 for local development (OS X)</title>
        <link rel="alternate" type="text/html" href="https://tech.finn.no2015/09/25/setup-nginx-with-http2-for-local-development/"/>
        <updated>2015-09-25T21:57:00+00:00</updated>
        <id>https://tech.finn.no/2015/09/25/setup-nginx-with-http2-for-local-development</id>
        <content type="html">&lt;p&gt;HTTP/2 became an official standard in May earlier this year, and support is starting to land in servers already. The most recent, and very welcome addition, is nginx 1.9.5 &lt;a href=&quot;https://www.nginx.com/blog/nginx-1-9-5/&quot;&gt;[1]&lt;/a&gt;. What makes nginx extra awesome is that it’s very easy to set up in front of any other HTTP 1.x server (or HTTP/2 for that matter). Server push won’t work just yet, but at least we can start to test how multiplexing works. Multiplexing is said to eliminate the need to concatenate resources into bundles. At FINN.no we do multiple releases a day, and it just feels wrong that users have to download the whole 100+ kB JavaScript bundle after every release, even though most of the time only a few lines have changed. If we don’t need to bundle anymore, the users only need to download the few scripts that had changed since their last visit!&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://http2.github.io/http2-spec/#TLSUsage&quot;&gt;HTTPS over TLS 1.2 is a requirement to use HTTP/2&lt;/a&gt;. &lt;a href=&quot;http://www.w3.org/TR/service-workers/#security-considerations&quot;&gt;Service Workers also require secure connections&lt;/a&gt;, and probably &lt;a href=&quot;https://w3c.github.io/webappsec/specs/powerfulfeatures/&quot;&gt;other features soon&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;This guide will help you set up nginx for local development on OS X, with &lt;a href=&quot;https://www.nginx.com/resources/admin-guide/reverse-proxy/&quot;&gt;proxy passing&lt;/a&gt; requests to your local server on port 8080 (or whichever port you prefer). In plain English, that means we put nginx in between your browser and your local development server. Your browser communicates securely over HTTP/2 to nginx, and nginx forwards the requests to your local server over unsecured HTTP/1.1.&lt;/p&gt;

&lt;h2 id=&quot;installing-nginx&quot;&gt;Installing nginx&lt;/h2&gt;

&lt;p&gt;You have to compile nginx with the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;--with-http_v2_module&lt;/code&gt; configuration parameter, but &lt;a href=&quot;http://brew.sh/&quot;&gt;Homebrew&lt;/a&gt; makes that a breeze. It’s one of my favorite tools on OS X.&lt;/p&gt;

&lt;p&gt;If you don’t have Homebrew, see &lt;a href=&quot;#install-homebrew&quot;&gt;Install Homebrew&lt;/a&gt; further down.&lt;/p&gt;

&lt;p&gt;To compile and install nginx with http2:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;brew update   &lt;span class=&quot;c&quot;&gt;# update list of packages&lt;/span&gt;
&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;brew &lt;span class=&quot;nb&quot;&gt;install&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;--with-http2&lt;/span&gt; nginx&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Test that the install works (make sure port 8080 is available):&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;sudo &lt;/span&gt;nginx
&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;open http://localhost:8080/&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;You should see a page with the title “Welcome to nginx!”&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;&lt;span class=&quot;c&quot;&gt;# stop nginx&lt;/span&gt;
&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;sudo &lt;/span&gt;nginx &lt;span class=&quot;nt&quot;&gt;-s&lt;/span&gt; stop&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Now it’s time to set up the https server with HTTP/2 enabled. Open /usr/local/etc/nginx/nginx.conf in an editor, and comment out the existing &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;server&lt;/code&gt; section. Then copy-paste in the config below instead&lt;sup&gt;&lt;a href=&quot;https://ma.ttias.be/enable-http2-in-nginx/&quot;&gt;[2]&lt;/a&gt;&lt;/sup&gt;. If your local server runs on a different port than 8080, you can change it in the proxy_pass URL.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-nginx&quot; data-lang=&quot;nginx&quot;&gt;&lt;span class=&quot;k&quot;&gt;server&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;kn&quot;&gt;listen&lt;/span&gt;                     &lt;span class=&quot;mi&quot;&gt;443&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;ssl&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;http2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;kn&quot;&gt;server_name&lt;/span&gt;                &lt;span class=&quot;s&quot;&gt;localhost&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;kn&quot;&gt;ssl&lt;/span&gt;                        &lt;span class=&quot;no&quot;&gt;on&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;kn&quot;&gt;ssl_protocols&lt;/span&gt;              &lt;span class=&quot;s&quot;&gt;TLSv1&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;TLSv1.1&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;TLSv1.2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;kn&quot;&gt;ssl_certificate&lt;/span&gt;            &lt;span class=&quot;s&quot;&gt;cert.pem&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;kn&quot;&gt;ssl_certificate_key&lt;/span&gt;        &lt;span class=&quot;s&quot;&gt;cert.key&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;kn&quot;&gt;location&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;kn&quot;&gt;proxy_pass&lt;/span&gt;          &lt;span class=&quot;s&quot;&gt;http://localhost:8080&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;kn&quot;&gt;proxy_set_header&lt;/span&gt;    &lt;span class=&quot;s&quot;&gt;Host&lt;/span&gt;      &lt;span class=&quot;nv&quot;&gt;$host&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;kn&quot;&gt;proxy_set_header&lt;/span&gt;    &lt;span class=&quot;s&quot;&gt;X-Real-IP&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$remote_addr&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;kn&quot;&gt;proxy_set_header&lt;/span&gt;    &lt;span class=&quot;s&quot;&gt;X-HTTPS&lt;/span&gt;   &lt;span class=&quot;s&quot;&gt;'True'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;To use https we need to generate a self signed certificate. It will give you a warning in the browser, but it works fine for local development. This command will generate the certificate &lt;sup&gt;&lt;a href=&quot;https://ma.ttias.be/how-to-create-a-self-signed-ssl-certificate-with-openssl/&quot;&gt;[3]&lt;/a&gt;&lt;/sup&gt;:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;cd&lt;/span&gt; /usr/local/etc/nginx/
&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;sudo &lt;/span&gt;openssl req &lt;span class=&quot;nt&quot;&gt;-x509&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-sha256&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-newkey&lt;/span&gt; rsa:2048 &lt;span class=&quot;nt&quot;&gt;-keyout&lt;/span&gt; cert.key &lt;span class=&quot;nt&quot;&gt;-out&lt;/span&gt; cert.pem &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
   &lt;span class=&quot;nt&quot;&gt;-days&lt;/span&gt; 1024 &lt;span class=&quot;nt&quot;&gt;-nodes&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-subj&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;'/CN=local.finn.no'&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;When asked for «Common Name», fill in the hostname you use locally. Most FINN.no developers use &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;local.finn.no&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;To set up nginx to start automatically on boot, &lt;a href=&quot;https://github.com/Homebrew/homebrew-services&quot;&gt;Homebrew Services&lt;/a&gt; can set it up very easily.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;&lt;span class=&quot;c&quot;&gt;# install Homebrew Services&lt;/span&gt;
&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;brew tap homebrew/services&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;It is required to run nginx as root to open ports under 1024, and we want it to run on port 443. Homebrew Services will set it up correctly just by using sudo.&lt;/p&gt;

&lt;p&gt;Start nginx (and install to /Library/LaunchDaemons):&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;sudo &lt;/span&gt;brew services start nginx&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Homebrew Services can also be used to stop and restart nginx&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;sudo &lt;/span&gt;brew services stop nginx
&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;sudo &lt;/span&gt;brew services restart nginx&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Now you should be able to open &lt;a href=&quot;https://localhost/&quot;&gt;https://localhost/&lt;/a&gt; and nginx should proxy pass to your local development server.&lt;/p&gt;

&lt;p&gt;If you get a certificate warning, then it is working. It is only because you’re using the self signed certificate and not one signed by a certificate authority. Most browsers allow you to ignore the warning.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/2015-09-25-setup-nginx-with-http2-for-local-development/chrome-cert-warn-1.png&quot; alt=&quot;Certificate warning. Click the “Advanced” link in the lower left corner.&quot; /&gt;&lt;/p&gt;

&lt;p&gt;By adding the self signed certificate as a trusted certificate in System Keychain, we’ll get the green lock icon &lt;sup&gt;&lt;a href=&quot;http://apple.stackexchange.com/questions/80623/import-certificates-into-system-keychain-via-the-command-line&quot;&gt;[4]&lt;/a&gt;&lt;/sup&gt;:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;sudo &lt;/span&gt;security add-trusted-cert &lt;span class=&quot;nt&quot;&gt;-d&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-r&lt;/span&gt; trustRoot &lt;span class=&quot;nt&quot;&gt;-k&lt;/span&gt; /Library/Keychains/System.keychain /usr/local/etc/nginx/cert.pem&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;&lt;img src=&quot;/images/2015-09-25-setup-nginx-with-http2-for-local-development/chrome-green-lock.png&quot; alt=&quot;Green lock icon beside the hostname&quot; title=&quot;Yes, I do like tabs 🙈&quot; /&gt;&lt;/p&gt;

&lt;p&gt;If it doesn’t work, see the &lt;a href=&quot;#troubleshooting&quot;&gt;Troubleshooting&lt;/a&gt; section further down.&lt;/p&gt;

&lt;p&gt;To see that you really are using HTTP/2 in Chrome, you have to open the Network tab in Developer Tools. Right click (or CTRL-click) the column heading above the network requests, then make sure &lt;em&gt;Protocol&lt;/em&gt; is checked.
&lt;img src=&quot;/images/2015-09-25-setup-nginx-with-http2-for-local-development/chrome-show-protocol.png&quot; alt=&quot;How to add a protocol column in Chrome Dev-tools&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Then you should see HTTP/2 traffic show up as &lt;em&gt;h2&lt;/em&gt; in the protocol column.
&lt;img src=&quot;/images/2015-09-25-setup-nginx-with-http2-for-local-development/chrome-protocol-column.png&quot; alt=&quot;Screenshot of the protocol column showing network traffic as “h2”&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Congratulations, you now have HTTP/2 and HTTPS working!&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Thanks to Sveinung Røsaker, Rune Halvorsen, Tor Arne Kvaløy, Frode Risøy and Martin Solli for feedback and tips&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Update Oct. 1, 2015: How to make the self-signed certificate trusted. Replaced custom aliases with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;brew services&lt;/code&gt; and added tips for common problems.
Update Jun. 20, 2016: Updated brew install command with the new nginx options.&lt;/p&gt;

&lt;p&gt;Credit:&lt;/p&gt;
&lt;ol&gt;
  &lt;li&gt;&lt;a href=&quot;https://www.nginx.com/blog/nginx-1-9-5/&quot;&gt;https://www.nginx.com/blog/nginx-1-9-5/&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://ma.ttias.be/enable-http2-in-nginx/&quot;&gt;https://ma.ttias.be/enable-http2-in-nginx/&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://ma.ttias.be/how-to-create-a-self-signed-ssl-certificate-with-openssl/&quot;&gt;https://ma.ttias.be/how-to-create-a-self-signed-ssl-certificate-with-openssl/&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;http://apple.stackexchange.com/questions/80623/import-certificates-into-system-keychain-via-the-command-line&quot;&gt;http://apple.stackexchange.com/questions/80623/import-certificates-into-system-keychain-via-the-command-line&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h2 id=&quot;extra&quot;&gt;Extra&lt;/h2&gt;

&lt;h3 id=&quot;install-homebrew&quot;&gt;Install Homebrew&lt;/h3&gt;

&lt;p&gt;Run this one-liner in Terminal to install Homebrew:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;ruby &lt;span class=&quot;nt&quot;&gt;-e&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;$(&lt;/span&gt;curl &lt;span class=&quot;nt&quot;&gt;-fsSL&lt;/span&gt; https://raw.githubusercontent.com/Homebrew/install/master/install&lt;span class=&quot;si&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;

&lt;span class=&quot;c&quot;&gt;# if you haven't installed Command Line Developer Tools from Apple already&lt;/span&gt;
&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;xcode-select &lt;span class=&quot;nt&quot;&gt;--install&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;h3 id=&quot;troubleshooting&quot;&gt;Troubleshooting&lt;/h3&gt;

&lt;h4 id=&quot;connection-refused&quot;&gt;Connection refused&lt;/h4&gt;
&lt;p&gt;A “Connection refused” error probably means that nginx is not running correctly.&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Check that the ports in nginx.conf is not already in use&lt;/li&gt;
  &lt;li&gt;Check that you run nginx as root (sudo)&lt;/li&gt;
  &lt;li&gt;Check the error.log: &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/usr/local/var/log/nginx/error.log&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h4 id=&quot;502-bad-gateway&quot;&gt;502 Bad Gateway&lt;/h4&gt;
&lt;p&gt;If you get a 502 error, nginx is running, but nginx is not able to connect to your development server. Check that your development server is running, and that you have the correct port in nginx.conf. Copy the value of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;proxy_pass&lt;/code&gt; and try to open it in your browser. For the config above, that would be &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;http://localhost:8080&lt;/code&gt;.&lt;/p&gt;
</content>
        
        <author>
            <name>Gregers Rygg</name>
            
            <email></email>
            
        </author>
        
    </entry>
    
    <entry>
        <title>The Process of Using Kafka</title>
        <link rel="alternate" type="text/html" href="https://tech.finn.no2015/09/22/the-process-of-using-kafka/"/>
        <updated>2015-09-22T13:58:41+00:00</updated>
        <id>https://tech.finn.no/2015/09/22/the-process-of-using-kafka</id>
        <content type="html">&lt;p&gt;&lt;em&gt;Use and misuse&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Actually, the title is a bit too broad. We’ll look at how different number of acks, async and timeouts will affect the reliability of your messages.&lt;/p&gt;

&lt;h2 id=&quot;abstract--tldr&quot;&gt;Abstract / TL;DR&lt;/h2&gt;
&lt;p&gt;Kafka can be used in many ways, and there are also many options that you need to understand and decide upon before you start using Kafka. As always, the combination of options you choose is a compromise - a trade off - and in my opinion, explicit trade offs are much better than accidential ones.&lt;/p&gt;

&lt;p&gt;We have seen many ways of using Kafka internally, and to demonstrate the properties of some of the different permutations, we’ve run a small test to demonstrate how it works.&lt;/p&gt;

&lt;p&gt;Before using Kafka, you need to understand it! Read the &lt;a href=&quot;http://kafka.apache.org/documentation.html&quot;&gt;documentation&lt;/a&gt;, the &lt;a href=&quot;http://www.confluent.io/blog&quot;&gt;confluent.io blog&lt;/a&gt;, and &lt;a href=&quot;https://martin.kleppmann.com/2015/05/27/logs-for-data-infrastructure.html&quot;&gt;Martin Kleppman’s blog&lt;/a&gt;.&lt;/p&gt;

&lt;h2 id=&quot;configuration-details&quot;&gt;Configuration Details&lt;/h2&gt;

&lt;p&gt;For a topic, you’ll have to choose a replication factor and number of partitions. In this small exercise will fix the replication factor at 3, we’ll split each topic in 4 partitions.&lt;/p&gt;

&lt;p&gt;You can either produce to a topic synchronously or asynchronously. In the first case, the message is sent on the same thread as your producer code. In the async case, the kafka producer library will put your request in an internal queue, and unless that queue is full, you’ll get control back to your main thread immediately.&lt;/p&gt;

&lt;p&gt;In the synchronous case you have to choose how many servers need to acknowledge the message before your thread can continue. The fastest case is to not wait for any acknowledgement. That means, your thread continues immediately after the message is sent on the network to leader. If the network goes down - your message is lost. For a higher level of reliability you can wait for acknowledgement from the partition leader, and finally, you can wait for acknowledgement from all in-sync-replicas.&lt;/p&gt;

&lt;p&gt;The main trade off is thus between reliability - the possibility of losing data - and (you guessed it) speed. That is a business decision; is it acceptable to lose some data?&lt;/p&gt;

&lt;p&gt;These simple demonstrations were run on a laptop connected to our dev-cluster, consisting of 3 brokers. The response times we got are as expected - i.e matches the intuitive expectation, and clearly demonstrates the trade offs.&lt;/p&gt;

&lt;p&gt;The code connects to Kafka, does timing of the sending of 8 events, and then sends a big chunk of events. The various combinations of options in this exercise demonstrates actual patterns of code we have observed. Some of the combinations are NON-SAFE. Beware!&lt;/p&gt;

&lt;h2 id=&quot;timing-information&quot;&gt;Timing information&lt;/h2&gt;

&lt;p&gt;For various combinations of producer type (sync/async), &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;request.required.acks&lt;/code&gt; and ways of exiting the program, we’ve run the small test program included at the end of this post.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Run 1.1 - synchronous producer, wait for all in sync replicas to acknowledge the write. This is the most secure way of producing messages.&lt;/em&gt;&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;-----------------------------------------
ms     %     Task name
-----------------------------------------
00012  000%  send 1 kafka event - 0
00009  000%  send 1 kafka event - 1
00010  000%  send 1 kafka event - 2
00008  000%  send 1 kafka event - 3
00010  000%  send 1 kafka event - 4
00008  000%  send 1 kafka event - 5
00009  000%  send 1 kafka event - 6
00009  000%  send 1 kafka event - 7
82393  100%  send 10000 kafka events
00009  000%  closing
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;em&gt;Run 1.2 - synchronous producer, wait for the leader to acknowledge the write. If the leader crashes after acknowledging, but before any replica receives the data, the message will be lost.&lt;/em&gt;&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;-----------------------------------------
ms     %     Task name
-----------------------------------------
00006  000%  send 1 kafka event - 0
00005  000%  send 1 kafka event - 1
00007  000%  send 1 kafka event - 2
00007  000%  send 1 kafka event - 3
00009  000%  send 1 kafka event - 4
00014  000%  send 1 kafka event - 5
00009  000%  send 1 kafka event - 6
00007  000%  send 1 kafka event - 7
39344  100%  send 10000 kafka events
00008  000%  closing
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;em&gt;Run 1.3 - synchronous producer, but do not wait for acknowledgement.&lt;/em&gt;&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;-----------------------------------------
ms     %     Task name
-----------------------------------------
00001  000%  send 1 kafka event - 0
00001  000%  send 1 kafka event - 1
00000  000%  send 1 kafka event - 2
00001  000%  send 1 kafka event - 3
00002  000%  send 1 kafka event - 4
00001  000%  send 1 kafka event - 5
00000  000%  send 1 kafka event - 6
00001  000%  send 1 kafka event - 7
02093  099%  send 10000 kafka events
00007  000%  closing
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Let’s switch to the asynchronous producer. The producing thread will continue, but we’ll need to be aware of the need to drain any queues before exiting. Messages will be batched together within a 5 s window, so just exiting can be a serious problem if you care about your data.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Run 2.1 - Async, blocking on producer.close() before exiting:&lt;/em&gt;&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;-----------------------------------------
ms     %     Task name
-----------------------------------------
00001  000%  send 1 kafka event - 0
00000  000%  send 1 kafka event - 1
00000  000%  send 1 kafka event - 2
00000  000%  send 1 kafka event - 3
00000  000%  send 1 kafka event - 4
00000  000%  send 1 kafka event - 5
00000  000%  send 1 kafka event - 6
00000  000%  send 1 kafka event - 7
00065  005%  send 10000 kafka events
01302  095%  closing
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;100% of all messages were delivered.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Run 2.2 - Async, immediate System.exit()&lt;/em&gt;&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;-----------------------------------------
ms     %     Task name
-----------------------------------------
00000  000%  send 1 kafka event - 0
00000  000%  send 1 kafka event - 1
00000  000%  send 1 kafka event - 2
00000  000%  send 1 kafka event - 3
00000  000%  send 1 kafka event - 4
00000  000%  send 1 kafka event - 5
00000  000%  send 1 kafka event - 6
00000  000%  send 1 kafka event - 7
00220  100%  send 10000 kafka events
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;em&gt;0% of the messages were delivered!&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Run 2.3 - Async, main method returns normally, without calling close()&lt;/em&gt;&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;-----------------------------------------
ms     %     Task name
-----------------------------------------
00000  000%  send 1 kafka event - 0
00000  000%  send 1 kafka event - 1
00000  000%  send 1 kafka event - 2
00001  000%  send 1 kafka event - 3
00000  000%  send 1 kafka event - 4
00000  000%  send 1 kafka event - 5
00000  000%  send 1 kafka event - 6
00000  000%  send 1 kafka event - 7
00209  100%  send 10000 kafka events
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The process does not terminate, as there is a non-daemon producer thread left. The default value of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;queue.buffering.max.ms&lt;/code&gt; is 5000 ms, and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;queue.buffering.max.messages&lt;/code&gt; is 10.000 messages. This means that the queue will be sent after 10.000 messages, or after waiting at most 5000 ms.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Run 2.4 - Async, sleep 8 seconds after sending, then System.exit()&lt;/em&gt;&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;-----------------------------------------
ms     %     Task name
-----------------------------------------
00000  000%  send 1 kafka event - 0
00000  000%  send 1 kafka event - 1
00000  000%  send 1 kafka event - 2
00000  000%  send 1 kafka event - 3
00000  000%  send 1 kafka event - 4
00000  000%  send 1 kafka event - 5
00001  000%  send 1 kafka event - 6
00000  000%  send 1 kafka event - 7
00171  002%  send 10000 kafka events
08000  098%  sleeping
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;100% of the messages were delivered&lt;/p&gt;

&lt;p&gt;As 8 s is more than 5000 ms, the messages will be delivered.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Run 2.5 - Async, sleep 4 seconds after sending, then System.exit()&lt;/em&gt;&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;-----------------------------------------
ms     %     Task name
-----------------------------------------
00000  000%  send 1 kafka event - 0
00000  000%  send 1 kafka event - 1
00001  000%  send 1 kafka event - 2
00000  000%  send 1 kafka event - 3
00000  000%  send 1 kafka event - 4
00000  000%  send 1 kafka event - 5
00000  000%  send 1 kafka event - 6
00000  000%  send 1 kafka event - 7
00143  003%  send 10000 kafka events
04001  097%  sleeping
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;99.89% (9989/10000) of the messages were delivered.&lt;/p&gt;

&lt;p&gt;As 4 s is less than 5000 ms, the first 10.000 messages will be delivered, and the rest will be left in the queue. As we’ve sent more than 10.000 message during the session, some messages were lost.&lt;/p&gt;

&lt;h2 id=&quot;summary&quot;&gt;Summary&lt;/h2&gt;

&lt;p&gt;How you configure your Kafka producer will determine whether you have a super-fast-and-lose-everything messaging system, or something slower but reliable. YMMV.&lt;/p&gt;

&lt;p&gt;Before using Kafka, you need to understand it! Read the &lt;a href=&quot;http://kafka.apache.org/documentation.html&quot;&gt;documentation&lt;/a&gt;, the &lt;a href=&quot;http://www.confluent.io/blog&quot;&gt;confluent.io blog&lt;/a&gt;, and &lt;a href=&quot;https://martin.kleppmann.com/2015/05/27/logs-for-data-infrastructure.html&quot;&gt;Martin Kleppman’s blog&lt;/a&gt;. &lt;a href=&quot;http://hermes-pubsub.readthedocs.org/en/latest/&quot;&gt;Hermes&lt;/a&gt; is a message broker built on top of Kafka.&lt;/p&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;Milliseconds / event&lt;/th&gt;
      &lt;th&gt;What&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;8.2 ms&lt;/td&gt;
      &lt;td&gt;Sync, wait for all in sync replicas&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;3.9 ms&lt;/td&gt;
      &lt;td&gt;Sync, wait for leader&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;2.0 ms&lt;/td&gt;
      &lt;td&gt;Sync, don’t wait for ack&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;0.13 ms&lt;/td&gt;
      &lt;td&gt;Async, wait for the queue to finish&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;0.02 ms&lt;/td&gt;
      &lt;td&gt;Async, exit without waiting (aka /dev/null for the last messages)&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;h2 id=&quot;the-sample-code&quot;&gt;The sample code&lt;/h2&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-java&quot; data-lang=&quot;java&quot;&gt;&lt;span class=&quot;kn&quot;&gt;package&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;no&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;finntech&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;demo&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;ecclient&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;java.net.InetAddress&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;java.net.UnknownHostException&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;java.util.Date&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;java.util.Properties&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;java.util.Set&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;kafka.javaapi.producer.Producer&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;kafka.producer.KeyedMessage&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;kafka.producer.ProducerConfig&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;org.apache.commons.logging.Log&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;org.apache.commons.logging.LogFactory&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;org.springframework.util.StopWatch&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;CleanBulkSenderDemo&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;interface&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Acks&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;ALL_ISR&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;LEADER&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;NEVER_WAIT&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;static&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;main&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;[]&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;args&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;throws&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;InterruptedException&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;UnknownHostException&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;

        &lt;span class=&quot;kt&quot;&gt;boolean&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;featureAsync&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;featureSleep&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;kt&quot;&gt;boolean&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;featureClose&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;kt&quot;&gt;boolean&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;featureSystemExit&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;

        &lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;brokers&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;super-dev1.finntech.no:1337,super-dev2.finntech.no:1337,super-dev3.finntech.no:1337&quot;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;

        &lt;span class=&quot;kd&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Properties&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;props&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Properties&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;();&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;props&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;put&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;metadata.broker.list&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;brokers&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;props&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;put&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;producer.type&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;featureAsync&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;?&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;async&quot;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;sync&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;props&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;put&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;serializer.class&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;kafka.serializer.StringEncoder&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;props&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;put&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;request.required.acks&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;&quot;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Acks&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;ALL_ISR&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;props&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;put&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;client.id&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;InetAddress&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;getLocalHost&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;().&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;getHostName&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;());&lt;/span&gt;
        &lt;span class=&quot;nc&quot;&gt;Producer&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;producer&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Producer&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;ProducerConfig&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;props&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;

        &lt;span class=&quot;c1&quot;&gt;// connect, warm up&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;sendEvent&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;producer&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;42&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;42_0&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;sendEvent&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;producer&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;42&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;42_1&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;

        &lt;span class=&quot;nc&quot;&gt;System&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;out&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;println&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Starting&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;

        &lt;span class=&quot;nc&quot;&gt;StopWatch&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;sw&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;StopWatch&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;kafka&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;i&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;i&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;8&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;++)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;sw&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;start&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;send 1 kafka event - &quot;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;sendEvent&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;producer&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;42&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;42_&quot;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;sw&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;stop&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;();&lt;/span&gt;
        &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;

        &lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;n&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;10000&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;sw&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;start&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;send &quot;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;n&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot; kafka events&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;i&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;i&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;n&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;++)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;sendEvent&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;producer&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;42&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;x2_&quot;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
        &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;sw&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;stop&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;();&lt;/span&gt;

        &lt;span class=&quot;n&quot;&gt;sw&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;start&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;closing&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;featureClose&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;producer&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;close&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;();&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;featureSleep&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Thread&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;sleep&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;featureSleep&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;sw&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;stop&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;();&lt;/span&gt;

        &lt;span class=&quot;nc&quot;&gt;System&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;out&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;println&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;sw&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;prettyPrint&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;());&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;featureSystemExit&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;System&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;exit&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;kd&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;static&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;sendEvent&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;Producer&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;producer&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;s&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;s1&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;producer&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;send&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;KeyedMessage&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Test.ignore.performance&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;s&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;s1&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot; - &quot;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Date&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;()));&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;


&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

</content>
        
        <author>
            <name>Henning Spjelkavik</name>
            
        </author>
        
    </entry>
    
    <entry>
        <title>Browser statistics June 2015</title>
        <link rel="alternate" type="text/html" href="https://tech.finn.no2015/06/25/browser-statistics-june-2015/"/>
        <updated>2015-06-25T23:00:32+00:00</updated>
        <id>https://tech.finn.no/2015/06/25/browser-statistics-june-2015</id>
        <content type="html">&lt;p&gt;In &lt;a href=&quot;http://hjemmehos.finn.no/no/webfolk_+_entusiaster/finn_labs/FINN-statistikken+for+sommeren+2013.9UFRnSXl.ips&quot;&gt;July 2013 more than 40% of the visits to FINN.no&lt;/a&gt; were from a tablet or a phone. Nearly one year later, in April 2014 - &lt;a href=&quot;http://www.inma.no/ARTIKLER/Blogg/innlegg/FINN-statistikken-for-april-2014&quot;&gt;the share was 48%&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;What do you think has happened the last year?&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;In April, Google rolled out their &lt;a href=&quot;http://googlewebmastercentral.blogspot.no/2015/04/rolling-out-mobile-friendly-update.html&quot;&gt;“mobile-friendly update”&lt;/a&gt;, which boosts the ranking of sites that are mobile friendly in the Google search. We’re clearly not the only one seeing the increased market share of mobile devices.&lt;/p&gt;

&lt;h2 id=&quot;how-many-visitors-use-a-desktop-or-laptop&quot;&gt;How many visitors use a desktop or laptop?&lt;/h2&gt;

&lt;p&gt;Our first graph shows the share of our users using a mobile phone, tablet or a desktop computer to access FINN.no, regardless of whether they use the traditional somewhat-responsive-desktop-web (www.finn.no), our responsive mobile web (m.finn.no) or an Android or iPhone app. 62% of our visits are now from a smartphone or a tablet. &lt;em&gt;The traditional desktop/laptop has a market share of 38%.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;/images/2015-06-16-browser-statistics-june-2015/Visits per channel percent.png&quot;&gt;&lt;img class=&quot;center-block&quot; alt=&quot;Channel graph&quot; src=&quot;/images/2015-06-16-browser-statistics-june-2015/Visits per channel percent.png&quot; /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2 id=&quot;which-finnno-application-do-people-use&quot;&gt;Which FINN.no application do people use?&lt;/h2&gt;

&lt;p&gt;We have three major versions of FINN.no - the desktop, the responsive m.finn.no, and native apps. Our reponsive site is now the biggest! 14% of our visits are from our native apps, 42% from www.finn.no, and 44% from our responsive m.finn.no.&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;/images/2015-06-16-browser-statistics-june-2015/Visits pr application percent.png&quot;&gt;&lt;img class=&quot;center-block&quot; alt=&quot;Application graph&quot; src=&quot;/images/2015-06-16-browser-statistics-june-2015/Visits pr application percent.png&quot; /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2 id=&quot;browsers&quot;&gt;Browsers&lt;/h2&gt;

&lt;p&gt;First of all, let’s take a look at the numbers of the &lt;em&gt;browser vendors&lt;/em&gt;. The ranking is clear, Apple is the biggest, ahead of Google, Microsoft and Mozilla - and Opera is still in the top 5, representing more than an amazing 0.2% of our visits.&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;/images/2015-06-16-browser-statistics-june-2015/browser-types.jpg&quot;&gt;&lt;img class=&quot;center-block&quot; alt=&quot;All providers&quot; src=&quot;/images/2015-06-16-browser-statistics-june-2015/browser-types.jpg&quot; /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Do you remember the “browser wars”? When IE 8.0 ruled the web? In case you wondered which browser is the biggest on the “desktop” here’s the trend:&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;/images/2015-06-16-browser-statistics-june-2015/desktop.jpg&quot;&gt;&lt;img class=&quot;center-block&quot; alt=&quot;Browsers, Windows&quot; src=&quot;/images/2015-06-16-browser-statistics-june-2015/desktop.jpg&quot; /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The &lt;em&gt;complete browser statistics&lt;/em&gt;, across all applications are as follows:&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;/images/2015-06-16-browser-statistics-june-2015/browsers-all.jpg&quot;&gt;&lt;img class=&quot;center-block&quot; alt=&quot;All browsers&quot; src=&quot;/images/2015-06-16-browser-statistics-june-2015/browsers-all.jpg&quot; /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It seems like a good idea to make sure your website works well with Safari!&lt;/p&gt;

&lt;p&gt;In case you still have Internet Explorer 9 as your favourite, it’s still making it to the top 15, with a total share of 1.2% of our traffic, marginally behind Internet Explorer 10 with 1.4%. In 2015, poor IE8 is down to 0.3%.&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;/images/2015-06-16-browser-statistics-june-2015/ie-versions.jpg&quot;&gt;&lt;img class=&quot;center-block&quot; alt=&quot;All browsers&quot; src=&quot;/images/2015-06-16-browser-statistics-june-2015/ie-versions.jpg&quot; /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Thanks to Tobias Flatin, Chris Searle, Gregers G Rygg, Trond Hjorteland, and others for feedback.&lt;/em&gt;&lt;/p&gt;

</content>
        
        <author>
            <name>Henning Spjelkavik</name>
            
        </author>
        
    </entry>
    
    <entry>
        <title>When flying from Oslo to London, you don't want to go via Moscow</title>
        <link rel="alternate" type="text/html" href="https://tech.finn.no2015/06/15/removing-long-travels-in-the-flight-search/"/>
        <updated>2015-06-15T12:47:32+00:00</updated>
        <id>https://tech.finn.no/2015/06/15/removing-long-travels-in-the-flight-search</id>
        <content type="html">&lt;p&gt;If you &lt;a href=&quot;http://www.finn.no/reise/flybilletter/resultat?tripType=roundtrip&amp;amp;requestedOrigin=OSL.METROPOLITAN_AREA&amp;amp;requestedDestination=LON.METROPOLITAN_AREA&amp;amp;requestedOrigin2=&amp;amp;requestedDestination2=&amp;amp;requestedDepartureDate=11.04.2016&amp;amp;requestedReturnDate=13.04.2016&amp;amp;numberOfAdults=1&amp;amp;numberOfChildren=0&amp;amp;cabinType=economy&quot;&gt;want to travel from Oslo to London&lt;/a&gt;, you may end up getting results which include layovers in far-away places. Do you really want to spend a night at the airport in Moscow if you only want to go from Oslo to London? Probably not, but nonetheless, some of the results we receive at the &lt;a href=&quot;http://www.finn.no/reise/flybilletter/&quot;&gt;FINN Flight Search&lt;/a&gt; are those kind of travels. Those travels may be really cheap, ending up at the top of our result list as we sort by price, but they are mostly garbage and we do not want our users to be confused by those outliers. We want them to go away!&lt;/p&gt;

&lt;h2 id=&quot;the-old-solution&quot;&gt;The old solution&lt;/h2&gt;
&lt;p&gt;We have tried to fix this before. With limited success. A solution we used a couple of years back was something like this:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;x = 3 * Average of the three shortest flights
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;We removed results with duration &amp;gt; x. This works fine on short travels, like a two-hour flight from Oslo to London. With the given formula, all trips longer than 6 hours to London will be removed. That’s ok.&lt;/p&gt;

&lt;p&gt;However, it doesn’t work too well on longer flights. If you want to go from Oslo to Bangkok, you would probably spend at least 11 hours in the air. In that case, it would be too conservative to just remove the flights that are 33 hours or more.&lt;/p&gt;

&lt;h2 id=&quot;a-theoretical-solution&quot;&gt;A theoretical solution&lt;/h2&gt;
&lt;p&gt;The correct way to remove the outliers is by &lt;a href=&quot;http://en.wikipedia.org/wiki/Quartile&quot;&gt;finding the quartiles&lt;/a&gt;. When the quartiles are found, the upper fence is defined by&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;Upper fence = Q1 + 1.5 * IQR
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Where IQR is the Interquartile Range; Q3 - Q1.&lt;/p&gt;

&lt;p&gt;We are using &lt;a href=&quot;http://lucene.apache.org/solr/&quot;&gt;Solr&lt;/a&gt; at FINN, but the stats query in Solr does not give us the quartiles out of the box, so we would need to do a separate query to calculate the quartiles. Should be easy enough.&lt;/p&gt;

&lt;h2 id=&quot;what-is-too-long&quot;&gt;What is too long?&lt;/h2&gt;
&lt;p&gt;So what is a &lt;strong&gt;too long&lt;/strong&gt; flight? That’s not an easy question to answer. Racking our brains (and doing some guesswork), we came up with the following suggestions on what the upper fence on duration to different destinations should be. The durations are per leg:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;OSL - LON 300 mins = 5 hrs. Given the minimum flight time OSL - LON = 115  
OSL - BKK (Bangkok) 1000 mins = 17 hrs. Minimum = 674  
OSL - KOA (Kona, Hawaii) 2000 mins = 33 hrs. Minimum = 1335
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The minimum flight duration is clearly of interest to us. We store that away in Solr, and using the &lt;a href=&quot;http://wiki.apache.org/solr/StatsComponent&quot;&gt;stats query from Solr&lt;/a&gt; on that field, we get the following data on a flight OSL-LON:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&quot;stats_fields&quot;: {
      &quot;minimumLegDuration&quot;: {
        &quot;min&quot;: 115,
        &quot;max&quot;: 815,
        &quot;count&quot;: 3610,
        &quot;missing&quot;: 36,
        &quot;sum&quot;: 615655,
        &quot;sumOfSquares&quot;: 123968775,
        &quot;mean&quot;: 170.5415512465374,
        &quot;stddev&quot;: 72.5080446083144,
        &quot;facets&quot;: {}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;A lot of good information there! We get the standard deviation, mean and min values. Missing the quartiles, though, but we can fix that!&lt;/p&gt;

&lt;h3 id=&quot;and-what-is-too-short&quot;&gt;And what is too short?&lt;/h3&gt;

&lt;p&gt;Sometimes, the supplier sends us incorrect data. That’s bad. But the good thing is that when the data is incorrect it is &lt;em&gt;really&lt;/em&gt; incorrect. We sometimes receive flight times of 0 minutes. That is obviously wrong, and we filter out those. However, we have &lt;em&gt;not&lt;/em&gt; seen any occurences of, say, 1 minute flight times. That would be wrong as well, but more difficult to handle. So we close our eyes and don’t care about those.&lt;/p&gt;

&lt;h2 id=&quot;but-hang-on&quot;&gt;But hang on…&lt;/h2&gt;
&lt;p&gt;So, we were ready to go! We wanted to fix this problem once and for all. But before we started coding, we talked to emeritus &lt;a href=&quot;http://www.sv.uio.no/econ/english/people/aca/haraldg/index.html&quot;&gt;Harald Goldstein&lt;/a&gt; at the University of Oslo. He’s been teaching statistics at the university level for years and had some good feedback on our theoretical solution. First of all, he pointed out that the solution we have outlined is a good solution if we have a &lt;em&gt;symmetrical&lt;/em&gt; distribution of the flight durations. The quartiles and the standard deviation works well on a symmetrical distribution like the &lt;a href=&quot;https://en.wikipedia.org/wiki/Normal_distribution&quot;&gt;normal distribution&lt;/a&gt;, but what about &lt;em&gt;our&lt;/em&gt; data? Is it normal distributed? That was a relevant question, and as one of FINN’s main strategies is “data in our backbones”, we surely needed to have a closer look at this.&lt;/p&gt;

&lt;p&gt;So here goes, flights from Oslo to London. Trip duration on the x-axis, number of occurences on the y-axis:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/2015-06-09-removing-long-travels-in-the-flight-search/osl_lon_distribution.png&quot; alt=&quot;distribution&quot; title=&quot;tripDuration on the x-axis, number of occurences on the y-axis&quot; /&gt;&lt;/p&gt;

&lt;p&gt;The shortest trip duration OSL - LON is 230 minutes in the graph above (115 min per leg) and there is even an outlier - which is not visible - at 2750 minutes! However, it is quite clear that this is not a normal distribution. It might look more like a &lt;a href=&quot;https://en.wikipedia.org/wiki/Gamma_distribution&quot;&gt;gamma distribution&lt;/a&gt; for the part where x &amp;gt; 230.&lt;/p&gt;

&lt;p&gt;Darn!&lt;/p&gt;

&lt;h2 id=&quot;back-to-square-one&quot;&gt;Back to square one&lt;/h2&gt;
&lt;p&gt;All the fancy statistics didn’t provide the solution, but it helped us really understand the problem. Obviously, we needed to re-think.&lt;/p&gt;

&lt;h3 id=&quot;logarithm&quot;&gt;Logarithm&lt;/h3&gt;
&lt;p&gt;A problem with the solution from ages back (described at the beginning), was that it didn’t work very well on long hauls. The relation between the minimum trip duration OSL-BKK and OSL-LON is approx. 6:1. Alas, the upper fence must be relatively lower on longer trips. After some more brain-racking, we came up with the idea that we should look at this logarithmically.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/2015-06-09-removing-long-travels-in-the-flight-search/log.png&quot; alt=&quot;log&quot; title=&quot;a logartihmic graph&quot; /&gt;&lt;/p&gt;

&lt;p&gt;A first suggestion&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;log(min BKK)/log(min LON) = log(675)/log(115) = 2,83/2,06
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;However, the relation is too far away from 6:1 and we are not able to compensate for long hauls.&lt;/p&gt;

&lt;h3 id=&quot;square-root&quot;&gt;Square root&lt;/h3&gt;
&lt;p&gt;Finding quartiles doesn’t work, using a logarithmic approach doesn’t work very well. So what about the square root? That looks a bit like a logarithmic function.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/2015-06-09-removing-long-travels-in-the-flight-search/sqrt.png&quot; alt=&quot;sqrt&quot; title=&quot;Square root&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Let’s try&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;sqrt(min BKK)/sqrt(min LON) = sqrt(675)/sqrt(115) = 26/11
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;This is much closer to 6:1 than the logarithmic approach. The relation is relatively more correct for both short- and long hauls.&lt;/p&gt;

&lt;h2 id=&quot;the-final-solution&quot;&gt;The final solution&lt;/h2&gt;
&lt;p&gt;After some trial and error, we finally ended up with the simple formula of&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;x = min + A * sqrt(min)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;Where x is the upper fence, min is the minimum duration for that leg and A is a tunable constant.&lt;/p&gt;

&lt;p&gt;This formula seems to remove most of the unwanted outliers. Some examples (from our first iteration, with the constant A=25)&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;SVG-BGO. min = 70   =&amp;gt; x = 279  (4.5 hrs)
OSL-LON. min = 230  =&amp;gt; x = 609  (10 hrs)
OSL-BKK. min = 1350 =&amp;gt; x = 2269 (37 hrs)
OSL-KOA. min = 2671 =&amp;gt; x = 3963 (66 hrs)
OSL-NYC. min = 920  =&amp;gt; x = 1678 (28 hrs)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;That seems to work quite well. In the SVG (Stavanger) - BGO (Bergen) case, the formula removes approx. 30% of the offers. Some of them with a layover in Oslo.&lt;/p&gt;

&lt;p&gt;No need to hesitate then, this simple formula is now in our production systems. If you want to go from OSL to LON, you will get a slider which is preset with an upper fence of 5hrs 15min.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/2015-06-09-removing-long-travels-in-the-flight-search/enabled_filter.png&quot; alt=&quot;filter&quot; title=&quot;Enabled filter&quot; /&gt;&lt;/p&gt;

&lt;p&gt;You still have the possibility to slide it to the max, though. All the way up to 27hrs 30min. The result list will then show you the unfiltered result set. But nobody wants to use 27hrs 30min on a flight from OSL to LON, right?&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/2015-06-09-removing-long-travels-in-the-flight-search/disabled_filter.png&quot; alt=&quot;filter&quot; title=&quot;Disabled filter&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;conclusion&quot;&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;The simplest explanation is usually the correct one.&lt;/p&gt;
</content>
        
        <author>
            <name>Per Jørgen Walstrøm</name>
            
        </author>
        
    </entry>
    
    <entry>
        <title>Apache Cassandra in a Microservices Enterprise Platform</title>
        <link rel="alternate" type="text/html" href="https://tech.finn.no2015/04/28/Apache-Cassandra-in-a-Microservices-Enterprise-Platform/"/>
        <updated>2015-04-28T14:00:00+00:00</updated>
        <id>https://tech.finn.no/2015/04/28/Apache-Cassandra-in-a-Microservices-Enterprise-Platform</id>
        <content type="html">&lt;p&gt;In this article we’ll explore how Apache Cassandra, the world’s most popular wide column store and 8th most &lt;a href=&quot;http://db-engines.com/en/ranking&quot;&gt;popular&lt;/a&gt; database overall, will only grow as a cornerstone technology in a microservices platform. With a little theory to microservices, to some examples of microservices and underlying required infrastructure, we’ll show that any solution both capable of scaling and dealing with time-series data-models is going to need to depend upon Apache Cassandra as a persistence layer, despite having a polyglot persistence model at large.&lt;/p&gt;

&lt;h2 id=&quot;microservices&quot;&gt;microservices&lt;/h2&gt;

&lt;p&gt;Microservices is a term that’s come out of &lt;a href=&quot;http://martinfowler.com/articles/microservices.html&quot;&gt;ThoughtWorks’&lt;/a&gt; Martin Fowler and James Lewis. It’s a bit of a buzzword, basically a fresh revival of the parts of service orientated architecture that you should be focusing on and getting right. A lot of it hopefully is obvious to you already. If you’ve been doing service orientated architecture or even generally just unix programming properly over the years it might well be frustrating just how buzz “microservices” has become. But it’s worth keeping in mind how much garbage we’ve collected and how many aspects of service orientated architecture that we’ve gotten badly wrong over the years. Younger programmers certainly deserve the clarity that ThoughtWorks is giving us here.&lt;/p&gt;

&lt;p&gt;Microservices, following the tips and guidelines from Sam Newman, can basically be broken down into four groups.&lt;/p&gt;

&lt;h3 id=&quot;interfaces&quot;&gt;interfaces&lt;/h3&gt;

&lt;p&gt;Ensure that you standardise the systems architecture at large and especially the gaps or what we know as the APIs between services. Standardise upon practices and protocols that minimise coupling. Move from tightly coupled systems with many compile time dependencies and distributed published client libraries, to clearly defined and isolated runtime APIs. Take advantage of REST, especially level 3 in richardson’s maturity model, for the synchronous domain driven designed parts of your system. When it comes to event driven design use producer defined schemas, like that offered by Apache Thrift’s IDL which gives you embedded schemas for good forward and backward compatibility along with isolated APIs that prevent transitive dependencies creeping through your platform. Getting this right also means that within services teams get a lot more freedom and autonomy to implement as they like, which in turns diminishes the effects of Brook’s law.&lt;/p&gt;

&lt;h3 id=&quot;deployment&quot;&gt;deployment&lt;/h3&gt;

&lt;p&gt;Simplify deployment down to having just one way of deploying artifacts, of any type, to any environment. The process of deployment needs to be so easy that deploying continuously each and every change into production becomes standard practice. This often also requires some organisational and practical changes like moving to stable master codebases and getting developers comfortable with working with branches and &lt;a href=&quot;http://tech.finn.no/2013/06/20/dark-launching-and-feature-toggles/&quot;&gt;dark launching&lt;/a&gt;.&lt;/p&gt;

&lt;h3 id=&quot;monitoring&quot;&gt;monitoring&lt;/h3&gt;

&lt;p&gt;It isn’t just about the motto of “monitor everything” and to have all metrics and logs accessible in one central place, but to include synthetic requests to provide monitoring alerts that catch critical errors immediately, and to use correlation IDs to be able to easily put all the moving parts together for any one specific request.&lt;/p&gt;

&lt;h3 id=&quot;architectural-safety&quot;&gt;architectural safety&lt;/h3&gt;

&lt;p&gt;Addressing the fallacies of distributed computing, ensure that services are as available as possible and consumers handle failures gracefully by using such mechanisms as circuit breakers, load balancing, and bulkheads.&lt;/p&gt;

&lt;p&gt;If you want more than I can only highly recommend Sam Newman’s just published book on &lt;a href=&quot;http://shop.oreilly.com/product/0636920033158.do&quot;&gt;Building Microservices&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;img class=&quot;center-block&quot; width=&quot;150&quot; alt=&quot;building microservices&quot; src=&quot;/images/2015-04-28-Apache-Cassandra-in-a-Microservices-Enterprise-Platform/building-microservices.jpg&quot; /&gt;&lt;/p&gt;

&lt;p&gt;In this article, and when talking about microservices, i’m most interested in how Cassandra, one of the most popular databases in our industry and the database most realistic to the practical realities of distributed computing, comes into its own.
Here Cassandra is relevant to the monitoring and architectural safety aspects of microservices, from looking at how monitoring is typically time series data, a known strength for Cassandra, and looking into how modern distributed systems should be put together.&lt;/p&gt;

&lt;h2 id=&quot;turning-the-database-inside-out&quot;&gt;turning the database inside out&lt;/h2&gt;

&lt;p&gt;Having worked in the enterprise for over a decade it’s clear that the relational database is at juxtaposition to the rest of our industry. The way we code against the RDMS, layer domain logic upon it, and at various layers up through the stack add additional complexity and cyclic dependencies with caches that require invalidation, makes it all too obvious we’ve been doing things wrong. You can see it in plain sight when watching presentations where people show their wonderful service orientated architectures or microservices platforms, and despite talking about how their services scale, are fault-tolerant and resilent, maybe even throwing in some fancy messaging system, there is still in the corner of their diagrams the magic unicorn – the relational database. Amidst all the promotion and praise for BASE architectures, the acceptance that our own services and those infrastructural like Solr, Elastic Search, or Kafka, need to work with eventual consistency so to achieve performance and availability, the relational database somehow gets a free ride, an exception, to all this common sense.&lt;/p&gt;

&lt;p&gt;Martin Kleppman presented at Strange Loop last year and afterwards wrote an article &lt;a href=&quot;http://blog.confluent.io/2015/03/04/turning-the-database-inside-out-with-apache-samza/&quot;&gt;“Turning the database inside out with Apache Sanza”&lt;/a&gt; that properly hits the nail on the head, perfectly describing my own woes around why the relational database has ruined back-end programming for us. And he sums it up rather elegantly to that the replication mechanism to databases needs to come out and become its own integral and accepted component to our systems designs. And this externalised replication is what we call streams and event driven design, and it leads us to de-normalised datasets and more time-series data models.&lt;/p&gt;

&lt;p&gt;&lt;img class=&quot;center-block&quot; alt=&quot;Data flow&quot; src=&quot;https://confluentinc.files.wordpress.com/2015/03/slide-40.png?w=400&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;product-examples&quot;&gt;product examples&lt;/h2&gt;
&lt;p&gt;Let’s look at a few examples from FINN.no and see how these things work in practice.&lt;/p&gt;

&lt;p&gt;First of all we know that Cassandra has a number of known strengths over other databases, from dealing with large volumes of data and providing superior performance on both write and read performance, to time series data and time-to-live data.&lt;/p&gt;

&lt;p&gt;But one of the areas that’s not highlighted enough is that Cassandra’s CQL schema often provides a simpler schema over the SQL equivalent, something that’s easier to work with and for programmers today something that uses more natural types and fluent APIs.&lt;/p&gt;

&lt;p&gt;After all the whole point with your microservices platform is that you’re writing smaller and smaller services, and those smaller services each come with their own private data models. As services and their schemas get smaller and simpler we find that we don’t need relationships and constraints and all the other complexities that the RDMS has to offer. Rather the constructs available to us in CQL are superior, and faster.&lt;/p&gt;

&lt;p&gt;The first example is how we store the users search history. This shouldn’t be a product that needs to be explained to anyone. The CQL schema to this is incredibly simple and it takes advantage of the combination between partition and clustering keys. And it operates fast, just make sure to apply the “CLUSTERING ORDER BY” or you’ll be falling into a Cassandra anti-pattern where you’ll be left reading tons of tombstones each read.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-sql&quot; data-lang=&quot;sql&quot;&gt;&lt;span class=&quot;k&quot;&gt;CREATE&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;TABLE&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;users_search_history&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;user_id&lt;/span&gt;      &lt;span class=&quot;nb&quot;&gt;text&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;search_id&lt;/span&gt;    &lt;span class=&quot;n&quot;&gt;timeuuid&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;search_url&lt;/span&gt;   &lt;span class=&quot;nb&quot;&gt;text&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;description&lt;/span&gt;  &lt;span class=&quot;nb&quot;&gt;text&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;PRIMARY&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;KEY&lt;/span&gt;  &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;user_id&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;search_id&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;WITH&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;CLUSTERING&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;ORDER&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;BY&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;search_id&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;desc&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Another example is fraud detection, and while fraud detection is typically a complicated bounded context at large, breaking it down you may find individual components using small simple isolated schemas. Here we have a CQL schema, much simpler than its relational SQL schema counterpart not only because is it time-series using the clustering key, but using Cassandra’s collection type to store the scores of each of the rules calculated during the fraud detection’s expert rules system.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-sql&quot; data-lang=&quot;sql&quot;&gt;&lt;span class=&quot;k&quot;&gt;CREATE&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;TABLE&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ad_scores&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;adid&lt;/span&gt;        &lt;span class=&quot;nb&quot;&gt;bigint&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;updated&lt;/span&gt;     &lt;span class=&quot;n&quot;&gt;timeuuid&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;rules&lt;/span&gt;       &lt;span class=&quot;k&quot;&gt;map&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;text&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;int&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;PRIMARY&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;KEY&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;adid&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;updated&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;So it shouldn’t be of any surprise that Cassandra is going to hit the sweet spot for particular services in a number of different ways in any polyglot persistence platform. But bring it back to the bigger picture and we can look at how we can remove that magic unicorn we keep seeing in systems designs’ overviews.&lt;/p&gt;

&lt;h2 id=&quot;brewers-theorem&quot;&gt;brewer’s theorem&lt;/h2&gt;

&lt;p&gt;Looking at the &lt;a href=&quot;http://en.wikipedia.org/wiki/CAP_theorem&quot;&gt;CAP theorem&lt;/a&gt; you recognise that to build a BASE microservices platform it means building AP systems. When you look at Martin Kleppman’s message that the replication is its own concern in your BASE platform, when you look at domain driven design and how to focus keeping your services within clear bounded contexts and then taking it further to use event driven design to further break those bounded contexts apart, you see that it ties back to the CAP theorem and it is for the sake of scalability and performance and even just simplicity in design, a preference for availability over consistency. Looking into it deeper in how streaming solutions often still need to write to raw event stores, and similar to a event sourcing model when a service needs to bootstrap its de-normalised dataset from scratch from data beyond that to which is stored in the stream’s history, you can see there is a parallel to partition tolerance and how it, just like partition tolerance within the CAP theorem, is a hard fast requirement to any distributed architecture.&lt;/p&gt;

&lt;p&gt;&lt;img class=&quot;center-block&quot; alt=&quot;CAP theorem&quot; src=&quot;/images/2015-04-28-Apache-Cassandra-in-a-Microservices-Enterprise-Platform/cap.png&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Here’s a simple example of a web application (named “xxx”) making three synchronous requests to underlying services in our platform when the user logs in. One service call to do the authentication, and the other two to fetch user data due to that user data being stored/available in different back-end systems.&lt;/p&gt;

&lt;p&gt;&lt;img class=&quot;center-block&quot; width=&quot;400&quot; alt=&quot;Finn platform&quot; src=&quot;/images/2015-04-28-Apache-Cassandra-in-a-Microservices-Enterprise-Platform/finn-platform-1.png&quot; /&gt;&lt;/p&gt;

&lt;p&gt;It’s not difficult to see this isn’t a great design. First of all it’s keeping all the logic on how these services calls are initiated and how the data joined together high up in the presentation layer. It’s also not a great performer unless you’re willing to introduce concurrency code up in your presentation layer.&lt;/p&gt;

&lt;p&gt;The obvious thing to do is introduce an aggregate service so that the web app only needs to make two inner requests and much of the logic, including any concurrency code, is pushed down into the platform and into the bounded context where it belongs. Another thing that typically happens here is that a cache, one that requires invalidation, is added into the aggregate service to address performance and availability.&lt;/p&gt;

&lt;p&gt;&lt;img class=&quot;center-block&quot; width=&quot;400&quot; alt=&quot;Finn platform&quot; src=&quot;/images/2015-04-28-Apache-Cassandra-in-a-Microservices-Enterprise-Platform/finn-platform-2.png&quot; /&gt;&lt;/p&gt;

&lt;p&gt;But it’s a hack. Now you have more network traffic than before and more overall complexity, and just a poor and possibly very slow system of eventual consistency.&lt;/p&gt;

&lt;p&gt;There is a better way, imagine there was but one service and all the data in a shared schema and then bring the replication mechanism of the database out into a stream to realise this. That is de-normalise the data from the auxiliary user-profile service back into the original user service. What you end up with is faster, more available, better scaling solution. Once you’re in the swing of event driven design and de-normalised datasets this is simpler solution too.&lt;/p&gt;

&lt;p&gt;&lt;img class=&quot;center-block&quot; width=&quot;400&quot; alt=&quot;Finn platform&quot; src=&quot;/images/2015-04-28-Apache-Cassandra-in-a-Microservices-Enterprise-Platform/finn-platform-3.png&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;infrastructure-examples&quot;&gt;infrastructure examples&lt;/h2&gt;

&lt;p&gt;With some ideas of how Cassandra can become important for a successful microservices platform within product development let’s look into how Cassandra fits into the infastructure and operations side of things. A trap i suspect a lot of people are getting themselves into when starting off with microservices is that they haven’t got the infrastructure required in place first. Even if James Lewis and Sam Newman puts extra emphasis on the needs for deployment and monitoring tools it still can be all too easily overlooked just how demanding this really is. It’s not just about monitoring and logging everything and then making it available in a centralised place. It’s not just about having reproducible containers on an elastic platform, but about all the infrastructure tools and services being rock stable and equally elastic. You don’t want to be running a microservices platform and have crucial monitoring and logging tools fail on you, particularly in any crisis or in the middle of any critical operation.&lt;/p&gt;

&lt;p&gt;When it comes to correlation IDs a brilliant tool out there is Zipkin from Twitter. Zipkin provides for you in all your applications and services this correlation ID, a unique ID for each user request, which you can for example put into your log4j thread context or MDC and then via a tool like Kibana be able to put together all the logs from across your whole platform for one specific user request. But Zipkin goes a lot further than this, based off Google’s Dapper paper, it provides for you distributed tracing or profiling of these individual requests.&lt;/p&gt;

&lt;p&gt;&lt;img class=&quot;center-block&quot; alt=&quot;Zipkin&quot; src=&quot;/images/2015-04-28-Apache-Cassandra-in-a-Microservices-Enterprise-Platform/zipkin.jpg&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Naturally Zipkin can be put together with Cassandra, the best fit as it’s perfect for large volumes of time series data. We also use scribe for the sending of the trace messages from all the jvms throughout our platform over to the zipkin collector which then stores them into Cassandra.&lt;/p&gt;

&lt;p&gt;Below is the typical page in Zipkin. Under the list of services the first row is the user’s request, here we can see that it took 195ms. Then under that we can see when and how long all the individual service calls took place. We can see which back-end services are running properly in parallel and which service calls are sequential. Services like Solr, Elastic Search, the Kafka producers, and of course Cassandra, are all listed as well. Not only is this fantastic for keeping your platform tuned for performance but it’s a great tool for helping to figure out what’s going on with those slow requests you’ve got, for example in the top 5th percentile.&lt;/p&gt;

&lt;p&gt;&lt;img class=&quot;center-block&quot; width=&quot;400&quot; alt=&quot;Zipkin request page&quot; src=&quot;/images/2015-04-28-Apache-Cassandra-in-a-Microservices-Enterprise-Platform/zipkin-request-page.png&quot; /&gt;&lt;/p&gt;

&lt;p&gt;It’s also a great tool to help keep teams up to date with all the constantly evolving moving parts that exist in a microservices platform, something that’ll no doubt be outdated a week after any manual catalog documentation was written. This is especially useful for front end developers that usually haven’t the faintest idea what’s going on behind the scenes.&lt;/p&gt;

&lt;p&gt;This visualisation can also be offered from within the browser, both Firefox and Chrome have plugins, so that developers can see what’s happening near real-time as they make requests.&lt;/p&gt;

&lt;p&gt;&lt;img width=&quot;400&quot; class=&quot;center-block&quot; alt=&quot;Zipkin graph&quot; src=&quot;/images/2015-04-28-Apache-Cassandra-in-a-Microservices-Enterprise-Platform/zipkin-graph.png&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Something that we’re added to Zipkin is a cascasding job that runs nightly in our hadoop yarn cluster, that aggregates all the different traces made during the day and builds up a graph of the platform showing which services are calling services. In this graph on the left hand side you will see our web and batch applications, then to the right of that the microservices moving down the stack the further to the right you go. Legacy databases with shared schemas end up as big honey pots on the very right while databases with properly isolated schemas appear as satellites to the services that own them. If you’re undertaking a move towards event driven design then you’ll see the connections between services and especially across bounded contexts break apart, and you should see those bounded contexts become more grouped neighbourhoods for themselves.&lt;/p&gt;

&lt;p&gt;This cascading job that aggregates this data should now be available in the &lt;a href=&quot;https://github.com/twitter/zipkin&quot;&gt;original Twitter Github repository&lt;/a&gt;, otherwise you’ll find it in FINN’s fork of it.&lt;/p&gt;

&lt;p&gt;Another crucial infrastructure tool is Grafana, and the Graphite and StatsD stack underneath it. Grafana and Graphite is one of those must-have tools in your infrastructure, but the problem is it just doesn’t scale. Indeed the carbon and whisper components to graphite are dead in the water. We’ve hit this problem and looked into the alternatives, and there’s one interesting alternative out there based on Cassandra, which only makes sense as it’s another perfect match for time series data.&lt;/p&gt;

&lt;p&gt;The plugin to Graphite is called Cyanite and very simply replaces all the carbon and whisper components. In an earlier version it was quite limited and you couldn’t for example get wildcarded paths in graphite working, but it now bundles with Elastic Search to give you a fully functional Graphite.&lt;/p&gt;

&lt;div class=&quot;line&quot;&gt;
    &lt;img class=&quot;unit&quot; width=&quot;250&quot; alt=&quot;Graphite Carbon&quot; src=&quot;/images/2015-04-28-Apache-Cassandra-in-a-Microservices-Enterprise-Platform/graphite-carbon.png&quot; /&gt;
    &lt;img class=&quot;unit&quot; width=&quot;450&quot; alt=&quot;Graphite Cyanite&quot; src=&quot;/images/2015-04-28-Apache-Cassandra-in-a-Microservices-Enterprise-Platform/graphite-cyanite.png&quot; /&gt;
&lt;/div&gt;

&lt;p&gt;If you want to take a go at setting this up and see for yourself just how easy it is to get running, and how easily Grafana, Graphite, Cyanite, Elastic Search, and Cassandra, are configured together take a look at the GitHub repository &lt;a href=&quot;https://github.com/mbrannigan/docker-cyanite-grafana&quot;&gt;docker-cyanite-grafana&lt;/a&gt;. It’s a docker image – just run &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;build.sh&lt;/code&gt; and once everything has started up run &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;test.sh&lt;/code&gt; to start feeding in dummy metrics and test away all the grafana features you’re used to working with.&lt;/p&gt;

&lt;h2 id=&quot;a-compliment-to-the-modern-enterprise-platform&quot;&gt;a compliment to the modern enterprise platform&lt;/h2&gt;
&lt;p&gt;With this run through of just a few product and infrastructure examples it’s quickly obvious how useful Cassandra is to have established in any polyglot persistence model. And with an understanding on distributed computing it becomes even harder to deny Cassandra its natural home in the modern enterprise platform.&lt;/p&gt;
</content>
        
        <author>
            <name>mick</name>
            
            <email></email>
            
        </author>
        
    </entry>
    
    <entry>
        <title>Finding Gold in Big Data</title>
        <link rel="alternate" type="text/html" href="https://tech.finn.no2015/04/27/finding-gold-in-big-data/"/>
        <updated>2015-04-27T09:00:00+00:00</updated>
        <id>https://tech.finn.no/2015/04/27/finding-gold-in-big-data</id>
        <content type="html">&lt;p&gt;In the &lt;a href=&quot;/2012/08/06/foraging-in-the-landscape-of-big-data/&quot;&gt;previous article&lt;/a&gt; we introduced our own introduction into the world of Big Data, and explored what it meant for FINN. Here we’ll go into the technical depth about the implementation of our Big Data needs.&lt;/p&gt;

&lt;h2 id=&quot;rehashing-the-previous-article&quot;&gt;rehashing the previous article&lt;/h2&gt;
&lt;p&gt;FINN is a busy site, the busiest in Norway, and we display over 80 million ad pages each day. Back when it was around 50 million views per day, the old system responsible for collecting statistics was performing up to a thousand database writes per second during peak traffic. Like a lot of web applications we had a modern scalable presentation and logic tier based upon ten tomcat servers but just one not-so-scalable monster database sitting in the data tier. The procedure responsible for writing to the statistics table in the relational database was our biggest thorn. It had indeed gotten so bad that: during peak traffic; operations had to at the first sign of trouble turn off this database procedure – that is when users were getting the most traffic to their ads we had to stop collecting their statistics.&lt;/p&gt;

&lt;p&gt;At this time we were also in the process of modularising the FINN web application. The time was right to turn our statistics system into something modern and modular. We wanted an asynchronous, fault-tolerance, linearly scaling, and durable solution.&lt;/p&gt;

&lt;h2 id=&quot;the-design&quot;&gt;the design&lt;/h2&gt;
&lt;p&gt;The new design uses the Command Query Separation pattern by using two separate modules: one for the collecting of events and one for displaying statistics. The event collecting system achieves asynchronousity, scalability, and durability by using Scribe. The backend persistence and statistics module achieves all goals by using &lt;a href=&quot;http://cassandra.apache.org&quot;&gt;Cassandra&lt;/a&gt; and Thrift. As an extension of the &lt;a href=&quot;http://highscalability.com/blog/2009/10/13/why-are-facebook-digg-and-twitter-so-hard-to-scale.html&quot;&gt;push-on-change&lt;/a&gt; model: the event collection stores denormalised data and it is later aggregated and normalised to the views the statistics module requires; we use MapReduce jobs within a Hadoop cluster.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/2015-04-27-finding-gold-in-big-data/event-collection-overview.png&quot; alt=&quot;Event Statistics Overview&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;the-statistics-module&quot;&gt;the statistics module&lt;/h2&gt;
&lt;p&gt;At FINN all our modular architecture is built either upon REST or interfaces defined by Apache Thrift. We discourage direct database access from any front-end application. So our Statistics module simply exposes the aggregated read-optimised data out of cassandra, with a Thrift IDL like:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-java&quot; data-lang=&quot;java&quot;&gt;&lt;span class=&quot;n&quot;&gt;service&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;AdViewingsService&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;cm&quot;&gt;/** returns total viewings for a given adId */&lt;/span&gt;
    &lt;span class=&quot;kt&quot;&gt;long&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;fetchTotal&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;i64&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;adId&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;cm&quot;&gt;/** returns a list of viewings for the intervals between startTimestamp and endTimestamp **/&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;list&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;long&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;fetchRolled&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;i64&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;adId&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Interval&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;interval&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;i64&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;startTimestamp&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;4&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;i64&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;endTimestamp&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;kd&quot;&gt;enum&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Interval&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;YEAR&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;MONTH&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;DAY&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;HOUR&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;QUARTER_HOUR&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;h2 id=&quot;the-event-collection-module&quot;&gt;the event collection module&lt;/h2&gt;
&lt;p&gt;The collecting of events we wanted to happen asynchronously and in a fail-over safe manner so we chose a combination of thrift and &lt;a href=&quot;https://github.com/facebook/scribe&quot;&gt;Scribe&lt;/a&gt; from Facebook. Each event object, or bean, is a thrift defined object, and these are serialised using thrift into Base64 encoded strings and transported through the network via Scribe. The event collection module is nothing more than a Scribe sink and it dumps these Thrift event beans directly into Cassandra.&lt;/p&gt;

&lt;p&gt;Each event is schemaless in its &lt;code&gt;values&lt;/code&gt; field, it’s up to the application to decide what data to record, and is defined by Thrift like:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-java&quot; data-lang=&quot;java&quot;&gt;&lt;span class=&quot;n&quot;&gt;struct&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Event&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;cm&quot;&gt;/** different categories generally won't be mixed in the normalised views. */&lt;/span&gt;
    &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;required&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;string&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;category&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;required&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;string&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;subcategory&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;mi&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;required&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;map&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;values&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;For the persistence of events in Cassandra we store events in rows based on minute by minute buckets. The  partition key goes a little further and looks like &lt;code&gt;&amp;lt;minute-bucket&amp;gt;_&amp;lt;random-number&amp;gt;&lt;/code&gt;. The reason for the additional column random_number, named “partition” in the CQL (Cassandra Query Language) schema, is it ensures write and read load is distributed around the cluster at all times, rather than one node being a hotspot for any given minute. Then each event is stored within a clustering key “collected_timestamp”. The value columns are essentially the category, the subcategory, and the json map keys_and_values.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-sql&quot; data-lang=&quot;sql&quot;&gt;&lt;span class=&quot;k&quot;&gt;CREATE&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;TABLE&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;events&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;minute&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;text&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;partition&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;int&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;type&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;text&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;collected_timestamp&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;timeuuid&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;collected_minute&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;bigint&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;subcategory&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;text&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;keys_and_values&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;text&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;PRIMARY&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;KEY&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;((&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;minute&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;partition&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;type&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;collected_timestamp&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;CREATE&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;INDEX&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;collected_minuteIndex&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;ON&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;events&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;collected_minute&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;We have moved the category column in as the first clustering key since the aggregation jobs typically only scan one type of category at a time. In hindsight we might not have done this as it would be better to be able to use the DateTieredCompactionStrategy. Within this schema there are two timestamp that we have to work with, both the real_timestamp representing when the event happened and the collected_timestamp when the event got stored in Cassandra. Analytical jobs, like “how many bicycles were sold in Oslo on a specific day?”, are interested in the real_timestamp. While the incremental aggregation jobs are interested in aggregating just those events that have come into the system since the last incremental run.&lt;/p&gt;

&lt;h2 id=&quot;the-technologies&quot;&gt;the technologies&lt;/h2&gt;
&lt;p&gt;&lt;span class=&quot;image-wrap&quot; style=&quot;float: left&quot;&gt;&lt;img style=&quot;margin: 5px; border: 0px solid black&quot; src=&quot;http://avatar.identi.ca/8594-96-20100330175539.jpeg&quot; alt=&quot;Cassandra&quot; /&gt; &lt;/span&gt; Cassandra is truly amazing and refreshingly modern database: linear scalability, decentralised, elastic, fault-tolerant, durable; with a rich datamodel that provides often &lt;a href=&quot;http://maxgrinev.com/2010/07/12/do-you-really-need-sql-to-do-it-all-in-cassandra/&quot;&gt;superior&lt;/a&gt; approaches to joins, grouping, and ordering than traditional sql. The Scribe sink simply stores the Thrift event objects directly into Cassandra. After that we use Apache &lt;a href=&quot;http://hadoop.apache.org&quot;&gt;Hadoop&lt;/a&gt; to then in the background aggregate this denormalised data. The storing of denormalised data in this manner extends the &lt;a href=&quot;http://highscalability.com/blog/2009/10/13/why-are-facebook-digg-and-twitter-so-hard-to-scale.html&quot;&gt;push-on-change&lt;/a&gt; model, an approach far more scalable “in comparison with pull-on-demand model where data is stored normalized and combined by queries on demand – the classical relational approach”&lt;a href=&quot;http://maxgrinev.com/2010/07/12/do-you-really-need-sql-to-do-it-all-in-cassandra/&quot;&gt;&lt;sup&gt;1&lt;/sup&gt;&lt;/a&gt;. In hadoop our aggregation jobs piece-wise over time scan over the denormalised data, normalising in this case by each ad’s unique identifier, this normalised summation for each ad is then added to a separate table in Cassandra which uses counter columns and is optimised for query performance.&lt;/p&gt;

&lt;p&gt;Many of these incremental aggregation jobs could have been tackled with Spark streaming or Storm today. But 4 years ago when we started this project none of these technologies would have been capable of keeping up with our demands.&lt;span class=&quot;image-wrap&quot; style=&quot;float: right;&quot;&gt;&lt;img style=&quot;margin: 5px; border: 0px solid black&quot; width=&quot;80&quot; src=&quot;/images/2015-04-27-finding-gold-in-big-data/scribe.png&quot; alt=&quot;Apache Hadoop&quot; /&gt; &lt;/span&gt; Even today we’re happy that we have this system as our underlying base design, since there’s always aggregation jobs that can’t be solved with, or better solved with, streaming solutions. Having the raw events stored provides us a greater flexibility and a parallel to partition-tolerance when streaming solutions fail.&lt;/p&gt;

&lt;p&gt;Painting a wonderful picture, hopefully you can see it puts us one foot into an entirely new world of opportunity, but it would be a lie not to say the journey here has also come with its fair share of pain and anguish. The four key technologies we adopted here: Cassandra, Hadoop, Scribe, and Thrift; involve changes in the way we code and design.&lt;/p&gt;

&lt;p&gt;Cassandra, being a &lt;a href=&quot;http://www.slideshare.net/jericevans/cassandra-not-just-nosql-its-mosql&quot;&gt;noSQL&lt;/a&gt; database, or “Not only SQL”, takes some investing in to understand how datamodels are designed to suit queries instead of writes. We also been bitten by an unfortunate bug or two along the way. The first was immediately after the 1.0 release when compression was introduced and before we had a CQL schema and were storing serialised thrift events spearately in individual rows. With the compression we jumped on it a little too quickly, also modifying its default settings for chunk_length_kb to suit out skinny rows for the denormalised data. This hit a &lt;a href=&quot;https://issues.apache.org/jira/browse/CASSANDRA-3427&quot;&gt;bug&lt;/a&gt; leading to excessive memory usage bringing Cassandra nodes down. The Cassandra community was very quickly to the rescue and we were running again with hours. The second bug was again related to skinny rows. Each of our count objects were stored in individual rows resulting in a column family with billions of rows stored on each physical machine. This eventually &lt;a href=&quot;http://thread.gmane.org/gmane.comp.db.cassandra.user/24052&quot;&gt;blew&lt;/a&gt; up in our face and the &lt;a href=&quot;http://thread.gmane.org/gmane.comp.db.cassandra.user/24052&quot;&gt;fix&lt;/a&gt; was to disable bloom filters. It’s obvious to us now that skinny rows should be avoided, and you’ll see in the CQL schema presented above that it isn’t how we do it anymore.&lt;/p&gt;

&lt;p&gt;Hadoop is a beast of a monster, and despite bringing functional programming to a world of its own (don’t go thinking you are anything special because you’re tinkering around with Scala) in many ways much of the joy Cassandra gave us was undermined by having to understand what the heck was going on sometimes in Hadoop. &lt;span class=&quot;image-wrap&quot; style=&quot;float: right;&quot;&gt;&lt;img style=&quot;margin: 5px; border: 0px solid black&quot; width=&quot;80&quot; src=&quot;/images/2015-04-27-finding-gold-in-big-data/hadoop-elephant.jpg&quot; alt=&quot;Apache Hadoop&quot; /&gt; &lt;/span&gt; We were also using Hadoop in a slightly unusual way trying to run small incremental jobs on it with as high frequency as possible. In fact it could be that we have the fastest Hadoop cluster in the world with our set up of a purely volatile HDFS filesystem built solely with SSDs. We’ve also had plenty of headaches due to Hadoop’s centralised setup. Our Cassandra and Hadoop nodes co-existed on the same servers in the one cluster, providing data-locality – a key ingredient to good Hadoop performance. But these servers came with a faulty raid controller locking up the servers many times each day for over a year, it took HP over a year to “look” into the problem. For our Cassandra cluster it successfully proved how rock solid fault-tolerance it really has – just imagine the crisis to the company if your monster relational database was crashing from faulty hardware twice a day. But one of these faulty server was running the hadoop masters: namenode and jobtracker; so aggregation jobs were frequently crashing. Since then we’re moved these services to a small separate virtual server, together they use no more than 600mb memory.&lt;/p&gt;

&lt;p&gt;Using Thrift throughout our modular platform we’d already broken the camel’s back on it. But Scribe has given us plenty of problems. Like most code open sourced from Facebook it seems to be “code thrown over the wall”. It contains traces of private Facebook code, has very little logging+documentation+support, and has settings that rarely work without peculiar and exact combinations with other settings. We believed scribe to be fault tolerant when it simply wasn’t and we lost many eventsc when resending buffers after a downtime or pause period. We have since moved to Kafka, as our general messaging bus for all event driven design throughout our microservices platform.&lt;/p&gt;

&lt;p&gt;These problems have all since been addressed – they are but part of our story. Dealing with each new technology on its own was no big deal but a strong recommendation in hindsight is not to trial multiple new technologies in the one project. The reputation of that project could well fall to that of its weakest link.&lt;/p&gt;

&lt;h2 id=&quot;a-bright-prosperous-future&quot;&gt;a bright prosperous future&lt;/h2&gt;
&lt;p&gt;Of all these technologies it is Cassandra that has proved the most successful, and today is the essential and fundamental technology to our “big data” capabiilities.&lt;/p&gt;

&lt;p&gt;On top of this platform we’ve come a long way. Today we use predeominantly Spark over Hadoop, with Spark jobs submitted to our YARN cluster. Although more and more use-cases are only requiring Cassandra. Some of these examples are messaging inboxes for each user, storing users search history, fraud detection, mapping probabilities of ipaddresses to geographical locations, and providing collaborative filtering using Spark’s ALS algorithm.&lt;/p&gt;

</content>
        
        <author>
            <name>mick</name>
            
            <email></email>
            
        </author>
        
    </entry>
    
    <entry>
        <title>Eric Evans in the house</title>
        <link rel="alternate" type="text/html" href="https://tech.finn.no2015/03/10/eric-evans-in-the-house/"/>
        <updated>2015-03-10T14:37:49+00:00</updated>
        <id>https://tech.finn.no/2015/03/10/eric-evans-in-the-house</id>
        <content type="html">&lt;p&gt;In our continuing quest to break up our old monolith and create a more flexible and scalable architecture, we have realized that we need to look at some new tools and methods. One of the more promising approaches was &lt;a href=&quot;http://domainlanguage.com/ddd/&quot;&gt;Domain Driven Design&lt;/a&gt;, a term coined by &lt;a href=&quot;https://twitter.com/ericevans0&quot;&gt;Eric Evans&lt;/a&gt; in his &lt;a href=&quot;http://www.amazon.com/exec/obidos/ASIN/0321125215/domainlanguag-20&quot;&gt;Big Blue Book&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Quite a few people in FINN has read (at least parts of) the Domain-Driven Design book, and we did some preliminary efforts to map our domains and look at our bounded contexts. We quickly realized that this was a very complex exercise, even with highly motivated tech and product people present. We figured we needed some help to get us on the right track. If you need help and information about DDD, why not get it straight from the horse’s mouth? So we got Eric Evans to visit us for a week at the end of February.&lt;/p&gt;

&lt;p&gt;We needed to make the most of our time with Eric, so we set up a rather extensive agenda:&lt;/p&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;Day&lt;/th&gt;
      &lt;th&gt;Subject&lt;/th&gt;
      &lt;th&gt;Attending&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;Monday&lt;/td&gt;
      &lt;td&gt;Strategic Domain Driven Design Course&lt;/td&gt;
      &lt;td&gt;FINNs CTO, our functional product directors, the members of our enterprise architecture group including our Chief Enterprise Architect, several key developers and a couple of our international friends from Schibsted Classified Media&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Tuesday&lt;/td&gt;
      &lt;td&gt;Strategic Domain Driven Design Course cont’d&lt;/td&gt;
      &lt;td&gt;Same as Monday&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Wednesday&lt;/td&gt;
      &lt;td&gt;Workshop: Ad concept and our miscellaneous vertical&lt;/td&gt;
      &lt;td&gt;Chief Enterprise architect, EA group, Functional Product Director, Product Owner, Lead Developers&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Thursday&lt;/td&gt;
      &lt;td&gt;Workshop: Job vertical and company profile&lt;/td&gt;
      &lt;td&gt;Chief Enterprise architect, EA group, Functional Product Director, Product Owner, Lead Developers&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Friday&lt;/td&gt;
      &lt;td&gt;Presentation for everyone in FINN.no, plus workshop: Communications&lt;/td&gt;
      &lt;td&gt;Chief Enterprise architect, EA group, Functional Product Director, Product Owner, Lead Developers&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;The course on Monday and Tuesday was extremely helpful in getting everyone up to speed on the terms and method of strategic domain driven design.
&lt;img src=&quot;/images/2015-03-10-eric-evans-in-the-house/eric_flipover.jpg&quot; alt=&quot;Eric Evans on the flipover&quot; title=&quot;Eric Evans on the flipover&quot; /&gt;&lt;/p&gt;

&lt;p&gt;The workshops the rest of the week focused on understanding the challenges involved in each area, and trying to map the bounded contexts of the existing systems.&lt;/p&gt;

&lt;p&gt;Some lessons we learnt during the week.&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;When you are trying to create a context map, start with a very concrete example of how the system works. Which components interacts with each other, what data is passed between them, exactly what does the input and output look like&lt;/li&gt;
  &lt;li&gt;Use a &lt;em&gt;huge&lt;/em&gt; whiteboard&lt;/li&gt;
  &lt;li&gt;Plan to use at least a few hours on every session. Doing a context map for even a part of a system may take a lot longer than you think.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Then you can manage to turn something like this:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/2015-03-10-eric-evans-in-the-house/ad_management.jpg&quot; alt=&quot;ad management example&quot; title=&quot;Ad management example&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Into a nice map like this:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/2015-03-10-eric-evans-in-the-house/ad_management_context_map.jpg&quot; alt=&quot;ad context map&quot; title=&quot;Ad context map&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Our key findings on bounded contexts:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Bounded contexts represent a higher order logical representation of a solution&lt;/li&gt;
  &lt;li&gt;They may span several of FINN’s current “microservices”&lt;/li&gt;
  &lt;li&gt;They are useful for understanding the high level architecture, and how teams and solutions depend on each other&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
  &lt;p&gt;The context map is a tool for discussions, and the boundaries and relationships are subjective.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Sample map focused on small subset of our system.
&lt;img src=&quot;/images/2015-03-10-eric-evans-in-the-house/bounded_context_microservice.png&quot; alt=&quot;bounded contexts microservices&quot; title=&quot;Bounded contexts and microservices&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Example trying to map a larger part of our system.
&lt;img src=&quot;/images/2015-03-10-eric-evans-in-the-house/finn_context_map.png&quot; alt=&quot;bounded contexts microservices&quot; title=&quot;Bounded contexts and microservices&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Another thing we focused on was the concept of distilling the core. Figuring out which parts of your system that gives you a competitive advantage, and deserves extra attention. As part of this process it is also important to identify your supporting and generic domains. The tricky part is that it is really your business strategy that dictates what your core domain is.&lt;/p&gt;

&lt;p&gt;So to sum things up: Having Eric Evans on-site for such a long time gave us invaluable insight into DDD, and how to apply it in our own business. Together the bounded context map, and our identified domains can help us drive development in the right direction. They are tools for discussion, not a framework that removes the need for highly competent developers.&lt;/p&gt;

</content>
        
        <author>
            <name>Nicolai Høge</name>
            
            <email></email>
            
        </author>
        
    </entry>
    
    <entry>
        <title>JavaScript is evolving</title>
        <link rel="alternate" type="text/html" href="https://tech.finn.no2015/02/26/javascript-is-evolving/"/>
        <updated>2015-02-26T09:23:01+00:00</updated>
        <id>https://tech.finn.no/2015/02/26/javascript-is-evolving</id>
        <content type="html">&lt;p&gt;As a die-hard java-developer for several years I was quite annoyed by the fact that Java as a language lagged several years behind C#. (E.g. Java got lambdas last year, 6 years after &lt;a href=&quot;http://en.wikipedia.org/wiki/C_Sharp_%28programming_language%29&quot;&gt;C#&lt;/a&gt;, so standards are nice for compatibility, but tends to be slow to evolve.)&lt;/p&gt;

&lt;h2 id=&quot;polyfill&quot;&gt;Polyfill&lt;/h2&gt;
&lt;p&gt;Now, as a frontend-developer and getting to know JavaScript, I have realized that JavaScript is lagging even further behind. Take for example the everyday case of checking if a string contains another string:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-javascript&quot; data-lang=&quot;javascript&quot;&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;hello world&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;indexOf&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;hello&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;//=&amp;gt; true&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;This is awkward and you know it, however JavaScript is evolving. The next version ECMAScript 6 is in its final phase, and will have, among &lt;a href=&quot;https://github.com/zloirock/core-js#ecmascript-6&quot;&gt;several features&lt;/a&gt;, the following method:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-javascript&quot; data-lang=&quot;javascript&quot;&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;hello world&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;includes&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;hello&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;//=&amp;gt; true&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Another new nice feature is for finding the first item in an array. It used to be written like this:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-javascript&quot; data-lang=&quot;javascript&quot;&gt;&lt;span class=&quot;k&quot;&gt;for&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;i&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;i&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;array&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;length&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;++&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;){&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;array&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;].&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;something&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;===&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;something&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;){&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;array&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;];&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;And now it can be written like:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-javascript&quot; data-lang=&quot;javascript&quot;&gt;&lt;span class=&quot;nx&quot;&gt;array&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;find&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;it&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;it&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;something&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;===&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;something&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;ECMAScript 6 is feature complete, and is started to be supported by modern browser like Chrome, Firefox and Internet Explorer 11. As for older browsers, great polyfills like &lt;a href=&quot;https://github.com/zloirock/core-js&quot;&gt;core-js&lt;/a&gt; come to our rescue.&lt;/p&gt;

&lt;h2 id=&quot;transpile&quot;&gt;Transpile&lt;/h2&gt;
&lt;p&gt;While polyfills extend current objects with new methods, ECMAScript 6 also contains new language syntax. Let’s look at the arrow function (also known as fat arrow):&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-javascript&quot; data-lang=&quot;javascript&quot;&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;one&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;two&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;three&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;].&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;map&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;v&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;v&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;!&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Older browser will not understand “=&amp;gt;” (for a long time!), so this code has to be transpiled to ECMAScript 5:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-javascript&quot; data-lang=&quot;javascript&quot;&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;one&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;two&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;three&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;].&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;map&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;v&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;v&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;!&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;There are some great transcompilation tools out there, and the one we have used is &lt;a href=&quot;https://babeljs.io/&quot;&gt;Babel&lt;/a&gt;. You can even play around with it &lt;a href=&quot;https://babeljs.io/repl/&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;h3 id=&quot;scoped-variables&quot;&gt;Scoped variables&lt;/h3&gt;
&lt;p&gt;JavaScript will also get scoped variables (let and const):&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-javascript&quot; data-lang=&quot;javascript&quot;&gt;&lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;hello&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;Hello&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;if&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kc&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;hello&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;Inner scoped hello&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;This will transpile to:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-javascript&quot; data-lang=&quot;javascript&quot;&gt;&lt;span class=&quot;kd&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;hello&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;Hello&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kc&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;kd&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;_hello&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;Inner scoped hello&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;h3 id=&quot;classes&quot;&gt;Classes&lt;/h3&gt;
&lt;p&gt;And finally, JavaScript is getting support for classes (which is sugar around prototype), and the syntax looks like this:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-javascript&quot; data-lang=&quot;javascript&quot;&gt;&lt;span class=&quot;kd&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;Hello&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;kd&quot;&gt;constructor&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;hello&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;Hello World&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;nx&quot;&gt;getHello&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;hello&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;This will be transpiled to the following, not very readable, but working code:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-javascript&quot; data-lang=&quot;javascript&quot;&gt;&lt;span class=&quot;kd&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;_prototypeProperties&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;child&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;staticProps&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;instanceProps&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;staticProps&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;Object&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;defineProperties&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;child&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;staticProps&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;instanceProps&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;Object&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;defineProperties&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;child&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;prototype&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;instanceProps&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;};&lt;/span&gt;

&lt;span class=&quot;kd&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;_classCallCheck&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;instance&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;Constructor&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;!&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;instance&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;instanceof&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;Constructor&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;throw&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;TypeError&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;Cannot call a class as a function&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;};&lt;/span&gt;

&lt;span class=&quot;kd&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;Hello&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;Hello&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;nx&quot;&gt;_classCallCheck&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;Hello&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;hello&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;Hello World&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

  &lt;span class=&quot;nx&quot;&gt;_prototypeProperties&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;Hello&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;getHello&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;value&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;getHello&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;hello&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;writable&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;configurable&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;true&lt;/span&gt;
      &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
      &lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;

      &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;Hello&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;})();&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;h2 id=&quot;conclusion&quot;&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;ECMAScript 6 is here, and we should start using it. Older browser will be around for a long time, so I think we should get used to transpiling code on the web-platform.&lt;/p&gt;

&lt;p&gt;Tor Arne
Senior developer&lt;/p&gt;

&lt;h4 id=&quot;27feb2015-revised-with-improved-code-examples-from-discussion-on-reddit&quot;&gt;27.feb.2015: Revised with improved code-examples from discussion on &lt;a href=&quot;https://www.reddit.com/r/javascript/comments/2x900k/javascript_is_evolving/&quot;&gt;reddit&lt;/a&gt;.&lt;/h4&gt;
</content>
        
        <author>
            <name>Tor Arne Kvaløy</name>
            
            <email></email>
            
        </author>
        
    </entry>
    
    <entry>
        <title>Java 8 workshop at Finn.no</title>
        <link rel="alternate" type="text/html" href="https://tech.finn.no2015/02/16/java-8-workshop-at-finnno/"/>
        <updated>2015-02-16T07:50:16+00:00</updated>
        <id>https://tech.finn.no/2015/02/16/java-8-workshop-at-finnno</id>
        <content type="html">&lt;figure&gt;
  &lt;img src=&quot;/images/2015-02-06-java-8-workshopp-at-finnno/DSC_0091.JPG&quot; alt=&quot;Pondering on the right lambda for the job&quot; /&gt;
  &lt;figcaption&gt;Pondering on the right lambda for the job&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;With Java 8 comes a whole new set of language features. It even challenges the imperative coding-style of the Java programmer.&lt;/p&gt;

&lt;p&gt;Java is a core language in Finn.no. More and more of our java-modules are being built with Java 8, adopting new features of the language.
A workshop was warranted, with the goal of bringing every developer up to speed on the functional paradigm of Java 8.&lt;/p&gt;

&lt;p&gt;In Finn.no, we want programmers to do stuff like this&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-java&quot; data-lang=&quot;java&quot;&gt;&lt;span class=&quot;n&quot;&gt;db&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;fetchLastDayAds&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;()&lt;/span&gt;
  &lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;stream&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;()&lt;/span&gt;
  &lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;filter&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ad&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;bap-webstore&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;equals&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ad&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;getAdType&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;()))&lt;/span&gt;
  &lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;flatMap&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ad&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ad&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;getContacts&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;().&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;stream&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;())&lt;/span&gt;
  &lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;distinct&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;()&lt;/span&gt;
  &lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;flatMap&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;per&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nc&quot;&gt;Optional&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;ofNullable&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;contact&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;getEmail&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;()).&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;map&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;Stream:&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;of&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;orElseGet&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;Stream:&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;empty&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;))&lt;/span&gt;
  &lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;peek&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;contact&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;LOG&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;trace&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Sending notification to &quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;+&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;per&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;))&lt;/span&gt;
  &lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;forEach&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;sendNotification&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;While avoiding stuff like this&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-java&quot; data-lang=&quot;java&quot;&gt;&lt;span class=&quot;nc&quot;&gt;IntStream&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;iterate&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;i&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;i&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;%&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
         &lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;parallel&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;()&lt;/span&gt;
         &lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;distinct&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;()&lt;/span&gt;
         &lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;limit&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;10&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
         &lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;forEach&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;System&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;out&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;println&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;(Locking up all cores on a CPU is bad, and should only be done when the machine is right about to become &lt;a href=&quot;http://xkcd.org/1046/&quot;&gt;self-aware&lt;/a&gt; and turn against you.)&lt;/p&gt;

&lt;figure&gt;
  &lt;img src=&quot;/images/2015-02-06-java-8-workshopp-at-finnno/DSC_0092.JPG&quot; alt=&quot;Several lambda-arrows pointing in the right direction here&quot; /&gt;
  &lt;figcaption&gt;Several lambda-arrows pointing in the right direction here&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;We split the workshop into two half days. The first day was dedicated to streams and lambdas. Everyone seemed keen on getting those tests green (a rhyme!).&lt;/p&gt;

&lt;p&gt;Day 2 we raised the bar with Optional&lt;T&gt; and our in-house version of Either&amp;lt;L,R&amp;gt;. With these structures, much more code can be written functionally in a world where values might not exist (be null), and things may go wrong (throw exceptions).&lt;/T&gt;&lt;/p&gt;

&lt;figure&gt;
  &lt;img src=&quot;/images/2015-02-06-java-8-workshopp-at-finnno/DSC_0095.JPG&quot; alt=&quot;We need power.. lots of power!&quot; /&gt;
  &lt;figcaption&gt;We need power.. lots of power!&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;Both the word “monad” and the phrase “monadic domain” was uttered several times, but we still saw very few making the swoooosh-sound while flying a hand over their head (the internationally recognized sign of communicating that a topic is beyond mental capacity).&lt;/p&gt;

&lt;p&gt;This might mean that the timing was good, and developers are interested in the new features of Java 8.&lt;/p&gt;

&lt;p&gt;You may &lt;a href=&quot;https://github.com/mariatsji/java8-workshop.git&quot;&gt;checkout the project and do the tasks yourself&lt;/a&gt;, by making the failing tests green.&lt;/p&gt;
</content>
        
        <author>
            <name>Sjur Millidahl</name>
            
            <email></email>
            
        </author>
        
    </entry>
    
    <entry>
        <title>Finn.no wanted to split up their monolith, you won't believe what happens next</title>
        <link rel="alternate" type="text/html" href="https://tech.finn.no2015/02/10/summit/"/>
        <updated>2015-02-10T11:00:00+00:00</updated>
        <id>https://tech.finn.no/2015/02/10/summit</id>
        <content type="html">&lt;p&gt;After working several years with (successfully) breaking up the legacy monolith, called iAD, Finn.no wanted to add structure to this process, and make sure that all 20 development teams work towards the same target.
The target is a microservices architecture with small-ish autonomous services, owning their own data.&lt;/p&gt;

&lt;p&gt;With that in mind we arranged our own two-day architecture summit for all Lead Developers, with a combination of external and internal presenters. The focus was on microservices, domain driven design, event driven architectures and modern programming techniques.&lt;/p&gt;

&lt;p&gt;We started off the first day with a long session. Finn.no’s Chief Enterprise Architect &lt;a href=&quot;https://twitter.com/sverheughe&quot;&gt;Sebastian Verheughe&lt;/a&gt; wanted to communicate his vision.
This included topics such as strategic domain-driven-design, data-highways, and how to use event-driven architecture to further decouple our services.&lt;/p&gt;

&lt;p&gt;This theme continued, with the first external presenter. &lt;a href=&quot;https://twitter.com/janovesk&quot;&gt;Jan Ove Skogheim&lt;/a&gt; from &lt;a href=&quot;http://particular.net/&quot;&gt;Particular Software&lt;/a&gt; talked about his experiences from Rikstoto, and how they had broken up a monolith into multiple small services. He also talked about the composite-UI pattern.
&lt;img src=&quot;/images/2015-02-09-summit/skogheim.png&quot; alt=&quot;Jan Ove Skogheim&quot; title=&quot;Jan Ove Skogheim&quot; /&gt;&lt;/p&gt;

&lt;p&gt;After lunch we continued with a presentation by &lt;a href=&quot;https://twitter.com/mevviz&quot;&gt;Karsten Mevassvik&lt;/a&gt; from finn.no, of  the most important properties for a finn.no  microservice. These include:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Is RESTful&lt;/li&gt;
  &lt;li&gt;Owns its own data-store&lt;/li&gt;
  &lt;li&gt;Team ownership&lt;/li&gt;
  &lt;li&gt;Consumer Driven
&lt;img src=&quot;/images/2015-02-09-summit/legoland.png&quot; alt=&quot;legoland&quot; title=&quot;Legoland Principles&quot; /&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The first day of the summit was concluded with a presentation of the CQRS architectural pattern. This presentation was held by &lt;a href=&quot;https://twitter.com/idarborlaug&quot;&gt;Idar Borlaug&lt;/a&gt; and &lt;a href=&quot;https://www.linkedin.com/in/andreasberre&quot;&gt;Andreas Berre&lt;/a&gt; from WebStep.
They showed all the central concepts in CQRS, such as commands, event-stores, aggregates, sagas using very good examples to illustrate their concepts.&lt;/p&gt;

&lt;p&gt;The second day of the summit started off with some hardcore programming. The frameworks of choice was node.js and RxJava. &lt;a href=&quot;https://twitter.com/chriswk&quot;&gt;Christopher Kolstad&lt;/a&gt; started with an example of how a microservice could be implemented using Reactive Streams and RxJava. After that &lt;a href=&quot;https://twitter.com/phillipjohnsen&quot;&gt;Phillip Johnsen&lt;/a&gt; presented a case using node.js in a real time context.
These two talks gave a good introduction to new concepts that are not widely used in finn.no today.
&lt;img src=&quot;/images/2015-02-09-summit/reactive.png&quot; alt=&quot;reactive java&quot; title=&quot;Reactive Java&quot; /&gt;&lt;/p&gt;

&lt;p&gt;During the second day several lightning talks were held, whenever there was a gap in the program. The talks, included topics as diverse as:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Testing of MicroServices&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://github.com/finn-no/unleash&quot;&gt;Unleash - Feature Toggles&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;Event Driven Architecure in Finn.no&lt;/li&gt;
  &lt;li&gt;Docker and Microservices.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Two more presentations should be mentioned. Firstly, one of the app-developers at Finn.no &lt;a href=&quot;https://no.linkedin.com/in/haakongjersvikeriksen&quot;&gt;Haakon Eriksen&lt;/a&gt;, shared his opinions on what a perfect service looks like from a front-end developers viewpoint. The key point here was that the service developers should talk to their clients before and during development.&lt;/p&gt;

&lt;p&gt;The last presentation was held by &lt;a href=&quot;https://no.linkedin.com/in/geipette&quot;&gt;Geir Pettersen&lt;/a&gt; where he talked about his approach to software architecture, “growing software” using examples from the development of the new airline-tickets search.
&lt;img src=&quot;/images/2015-02-09-summit/walking_skeleton.png&quot; alt=&quot;Walking skeleton&quot; title=&quot;Walking Skeleton&quot; /&gt;&lt;/p&gt;

</content>
        
        <author>
            <name>Audun Fauchald Strand</name>
            
            <email></email>
            
        </author>
        
    </entry>
    
    <entry>
        <title>From Wordpress to Jekyll</title>
        <link rel="alternate" type="text/html" href="https://tech.finn.no2015/01/28/from-wordpress-to-jekyll/"/>
        <updated>2015-01-28T13:40:36+00:00</updated>
        <id>https://tech.finn.no/2015/01/28/from-wordpress-to-jekyll</id>
        <content type="html">&lt;p&gt;&lt;a href=&quot;http://jekyllrb.com/&quot;&gt;&lt;img src=&quot;/images/2015-01-28-from-wordpress-to-jekyll/jekyll.png&quot; alt=&quot;Jekyll&quot; /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Last year we decided to move our developer blog from &lt;a href=&quot;https://wordpress.org/&quot;&gt;Wordpress&lt;/a&gt; to &lt;a href=&quot;http://jekyllrb.com/&quot;&gt;Jekyll&lt;/a&gt;. One of the main reasons were the constant security vulnerabilities of Wordpress. We were hosting Wordpress on our own hardware. We pleased our operations team when they could shut down the service. &lt;a href=&quot;https://pages.github.com/&quot;&gt;Github&lt;/a&gt; provides hosting for Jekyll projects and that was one of many reasons we landed on that solution.&lt;/p&gt;

&lt;p&gt;Other reasons why Jekyll is a good fit:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;Static files (blazing fast)&lt;/li&gt;
  &lt;li&gt;Version controlled files (&lt;a href=&quot;http://git-scm.com/&quot;&gt;git&lt;/a&gt;) and the source is available &lt;a href=&quot;https://github.com/finn-no/tech.finn.no&quot;&gt;here&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://github.com/orgs/finn-no/people&quot;&gt;Github&lt;/a&gt; handles username and passwords&lt;/li&gt;
  &lt;li&gt;Easy to run &lt;a href=&quot;https://github.com/finn-no/tech.finn.no/blob/gh-pages/README.md&quot;&gt;locally&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;You can use your favourite editor to write in Markdown or just plain HTML&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Github restricts the number of plugins you can use down to this &lt;a href=&quot;https://pages.github.com/versions/&quot;&gt;list&lt;/a&gt;. We really just wanted a simple blog platform so that was no deal breaker. We even get both @Andersos (&lt;a href=&quot;https://github.com/blog/821&quot;&gt;@mentions&lt;/a&gt;) and :+1: (Emoji).&lt;/p&gt;

&lt;p&gt;We &lt;a href=&quot;https://github.com/finn-no/tech.finn.no/issues/1&quot;&gt;landed on&lt;/a&gt; using &lt;a href=&quot;https://disqus.com/&quot;&gt;Disqus&lt;/a&gt; for comments which is loaded on the &lt;a href=&quot;https://github.com/finn-no/tech.finn.no/blob/gh-pages/_layouts/post.html#L41&quot;&gt;client side&lt;/a&gt;. We made a &lt;a href=&quot;http://tech.finn.no/2010/01/01/design-template/&quot;&gt;design template post&lt;/a&gt;. We use that post to check out the design of different elements. That post is also a good starting point for people new to markdown. We are using &lt;a href=&quot;https://github.com/gjtorikian/html-proofer&quot;&gt;Html-proofer&lt;/a&gt; to test the HTML of the site. Html-proofer checks that all the urls and images on the site are working. Another tip for improving blogposts is to use a tool like &lt;a href=&quot;http://www.hemingwayapp.com/&quot;&gt;Hemingwayapp&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;This developer blog is a place where we at Finn.no can share what we are working on and our interests. If you have suggestions or ideas for what we should write about please leave a comment under this post.&lt;/p&gt;

&lt;p&gt;List of 10 most visited posts:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;http://tech.finn.no/2012/07/25/the-ultimate-view-tiles-3/&quot;&gt;The Ultimate view tiles 3&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;http://tech.finn.no/2011/04/08/xss-protection-whos-responsibility/&quot;&gt;XSS protection whos responsibility&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;http://tech.finn.no/2011/05/12/dependency-injection-with-constructors/&quot;&gt;Dependency injection with constructors&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;http://tech.finn.no/2013/06/06/conquering-the-kingdom-of-java-with-a-nodejs-trojan-horse/&quot;&gt;Conquering the kingdom of Java with a Node.js trojan horse&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;http://tech.finn.no/2012/05/21/when-do-you-know-how-much-testing-is-enough/&quot;&gt;When do you know how much testing is enough&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;http://tech.finn.no/2013/03/20/given-the-git/&quot;&gt;Given the git&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;http://tech.finn.no/2014/07/01/log4j2-in-production-making-it-fly/&quot;&gt;Log4j2 in production making it fly&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;http://tech.finn.no/2013/01/31/i-wish-i-knew-my-consumers-maven-reverse-dependency/&quot;&gt;I wish I knew my consumers Maven reverse dependency&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;http://tech.finn.no/2014/11/21/email-providers-in-norway/&quot;&gt;Email providers in Norway&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;http://tech.finn.no/2013/06/20/dark-launching-and-feature-toggles/&quot;&gt;Dark launching and feature toggles&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</content>
        
        <author>
            <name>Andersos</name>
            
            <email></email>
            
        </author>
        
    </entry>
    
    <entry>
        <title>Finn.no year in review 2014</title>
        <link rel="alternate" type="text/html" href="https://tech.finn.no2015/01/07/finnno-year-in-review-2014/"/>
        <updated>2015-01-07T10:30:17+00:00</updated>
        <id>https://tech.finn.no/2015/01/07/finnno-year-in-review-2014</id>
        <content type="html">&lt;p&gt;The data in this post was gathered by &lt;a href=&quot;https://twitter.com/hallheim&quot;&gt;Hans-Erik Hallheim&lt;/a&gt;, &lt;a href=&quot;https://twitter.com/henrikfaller&quot;&gt;Henrik Faller&lt;/a&gt; and &lt;a href=&quot;https://twitter.com/lenekallum&quot;&gt;Lene Kallum&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The three most popular ads in 2014 were &lt;a href=&quot;http://www.finn.no/finn/car/used/object?finnkode=48525725&quot;&gt;Audi A6&lt;/a&gt;,
&lt;a href=&quot;http://www.finn.no/finn/torget/annonse?finnkode=50430335&quot;&gt;Walkman wanted badly&lt;/a&gt; and
&lt;a href=&quot;http://www.finn.no/finn/torget/annonse?finnkode=46166704&quot;&gt;Skis with crap boots, used twice&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We had over 7,4 million sofa related searches in 2014 and around 7000 unique permutations containing sofa.&lt;/p&gt;

&lt;h2 id=&quot;most-popular-bits-and-pieces-searches&quot;&gt;Most popular bits and pieces searches&lt;/h2&gt;
&lt;ol&gt;
  &lt;li&gt;&lt;a href=&quot;http://m.finn.no/bap/forsale/search.html?q=sofa&quot;&gt;sofa&lt;/a&gt; – 3.511.379 searches&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;http://m.finn.no/bap/forsale/search.html?q=sykkel&quot;&gt;sykkel&lt;/a&gt; – 2.358.170 searches&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;http://m.finn.no/bap/forsale/search.html?q=kjøkken&quot;&gt;kjøkken&lt;/a&gt; – 1.639.527 searches&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;http://m.finn.no/bap/forsale/search.html?q=iPhone5&quot;&gt;iPhone5&lt;/a&gt; – 1.637.355 searches&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;http://m.finn.no/bap/forsale/search.html?q=spisebord&quot;&gt;spisebord&lt;/a&gt; – 1.618.430 searches&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;http://m.finn.no/bap/forsale/search.html?q=hagemøbler&quot;&gt;hagemøbler&lt;/a&gt; – 1.606.647 searches&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;http://m.finn.no/bap/forsale/search.html?q=kommode&quot;&gt;kommode&lt;/a&gt; – 1.602.206 searches&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;http://m.finn.no/bap/forsale/search.html?q=seng&quot;&gt;seng&lt;/a&gt; – 1.479.144 searches&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;http://m.finn.no/bap/forsale/search.html?q=sovesofa&quot;&gt;sovesofa&lt;/a&gt; – 1.473.228 searches&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;http://m.finn.no/bap/forsale/search.html?q=tv&quot;&gt;tv&lt;/a&gt; – 1.390.684 searches&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;http://m.finn.no/bap/forsale/search.html?q=q=Louis+Vuitton&quot;&gt;Louis Vuitton&lt;/a&gt; – 1.333.992 searches&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;http://m.finn.no/bap/forsale/search.html?q=iPhone&quot;&gt;iPhone&lt;/a&gt; – 1.317.897 searches&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;http://m.finn.no/bap/forsale/search.html?q=iPhone+5s&quot;&gt;iPhone 5s&lt;/a&gt; – 1.288.181 searches&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;http://m.finn.no/bap/forsale/search.html?q=retro&quot;&gt;retro&lt;/a&gt; – 1.273.710 searches&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;http://m.finn.no/bap/forsale/search.html?q=bord&quot;&gt;bord&lt;/a&gt; – 1.266.214 searches&lt;/li&gt;
&lt;/ol&gt;

&lt;h2 id=&quot;most-popular-bits-and-pieces-ads&quot;&gt;Most popular bits and pieces ads&lt;/h2&gt;
&lt;ol&gt;
  &lt;li&gt;&lt;a href=&quot;http://www.finn.no/finn/torget/annonse?finnkode=50430335&quot;&gt;Walkman wanted badly&lt;/a&gt; – 136.113 page views&lt;/li&gt;
  &lt;li&gt;Can you help us? (family that needs Christmas gifts) &lt;a href=&quot;http://www.vg.no/nyheter/innenriks/julen/familie-paa-fem-ba-om-julegavehjelp-paa-finn/a/23352166/&quot;&gt;Mentioned by VG.no&lt;/a&gt; Ad deleted – 128.728 page views&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;http://www.finn.no/finn/torget/annonse?finnkode=46166704&quot;&gt;Skis with crap boots, used twice&lt;/a&gt; – 126.538 page views&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;http://www.finn.no/finn/torget/annonse?finnkode=46724675&quot;&gt;Skibus to be given away&lt;/a&gt; Ad deleted – 93.295 page views&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;http://www.finn.no/finn/torget/annonse?finnkode=49021304&quot;&gt;40-foot wooden boat for free&lt;/a&gt; – 90.941 page views&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;http://www.finn.no/finn/torget/annonse?finnkode=42349021&quot;&gt;Collection ad with known clothing brands (Chanel, Louis Vuitton etc.)&lt;/a&gt; – 89.031 page views&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;http://www.finn.no/49172207&quot;&gt;Free horse&lt;/a&gt; Ad deleted – 79923 page views.&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;http://www.finn.no/finn/torget/annonse?finnkode=46536496&quot;&gt;Free horse&lt;/a&gt; Annonse slettet – 75.787 page views.&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;http://www.finn.no/finn/torget/annonse?finnkode=48490602&quot;&gt;Dogs that need a new home&lt;/a&gt; – 53.772 page views&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;http://www.finn.no/finn/torget/annonse?finnkode=48721668&quot;&gt;Eventful bed given away&lt;/a&gt; – 45.571 page views&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;http://www.finn.no/finn/torget/annonse?finnkode=53971319&quot;&gt;Cabin rent free&lt;/a&gt; – 43.251 page views&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;http://www.finn.no/finn/torget/annonse?finnkode=36324261&quot;&gt;Flaky bathroom scale&lt;/a&gt; – 37.927 page views&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;http://www.finn.no/finn/torget/annonse?finnkode=46589547&quot;&gt;Horse for sale&lt;/a&gt; – 37.189 page views&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;http://www.finn.no/finn/torget/annonse?finnkode=51124670&quot;&gt;Homemade toy&lt;/a&gt; – 36.853 page views&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;http://www.finn.no/finn/torget/annonse?finnkode=46152256&quot;&gt;Furniture for sale by Hotel Cæsar&lt;/a&gt; – 36.415 page views&lt;/li&gt;
&lt;/ol&gt;

&lt;h2 id=&quot;most-popular-real-estate-searches&quot;&gt;Most popular real estate searches&lt;/h2&gt;
&lt;ol&gt;
  &lt;li&gt;småbruk – 2.668.158 searches&lt;/li&gt;
  &lt;li&gt;gårdsbruk – 566.808 searches&lt;/li&gt;
  &lt;li&gt;drammen – 431.883 searches&lt;/li&gt;
  &lt;li&gt;lillestrøm – 326.503 searches&lt;/li&gt;
  &lt;li&gt;haugesund – 312.167 searches&lt;/li&gt;
  &lt;li&gt;oslo – 307.844 searches&lt;/li&gt;
  &lt;li&gt;fredrikstad – 282393 searches&lt;/li&gt;
  &lt;li&gt;jessheim – 277.422 searches&lt;/li&gt;
  &lt;li&gt;asker – 270.368 searches&lt;/li&gt;
  &lt;li&gt;sandnes – 238.302 searches&lt;/li&gt;
&lt;/ol&gt;

&lt;h2 id=&quot;most-popular-real-estate-ads&quot;&gt;Most popular real estate ads&lt;/h2&gt;
&lt;ol&gt;
  &lt;li&gt;&lt;a href=&quot;http://www.finn.no/finn/realestate/homes/object?finnkode=48655021&quot;&gt;Representation property with 51 rooms, Staubø, 25 million NOK&lt;/a&gt; Ad deleted – 364.780 page views&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;http://www.finn.no/finn/realestate/homes/object?finnkode=51514125&quot;&gt;Real estate, Kvål, 4,990 million NOK&lt;/a&gt; – 327.107 page views&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;http://www.finn.no/finn/realestate/homes/object?finnkode=52986875&quot;&gt;Movable apartment, Gamle Fredrikstad, 350.000 NOK&lt;/a&gt; – 214.382 page views&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;http://www.finn.no/finn/realestate/homes/object?finnkode=44669887&quot;&gt;Beach property, Bygdøy, 70 million NOK&lt;/a&gt; – 199.403 page views&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;http://www.finn.no/finn/realestate/homes/object?finnkode=51041450&quot;&gt;NY-inspired design apartment, Tøyen, 4,2 million NOK&lt;/a&gt; – 192.780 page views&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;http://www.finn.no/finn/realestate/homes/object?finnkode=47946692&quot;&gt;Beach property, Bygdøy, 43,5 million NOK&lt;/a&gt; – 141.758 page views&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;http://www.finn.no/finn/realestate/homes/object?finnkode=41609162&quot;&gt;Architect designed beach property, Snarøya, 38 million NOK&lt;/a&gt; – 136.616 page views&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;http://www.finn.no/finn/realestate/homes/object?finnkode=48079229&quot;&gt;Penthouse, Tjuvholmen, 39,5 million NOK&lt;/a&gt; – 123.159 page views&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;http://www.finn.no/finn/realestate/homes/object?finnkode=48376727&quot;&gt;Nordhaug farm, Bekkestua, 34,77 million NOK (ad deleted)&lt;/a&gt; – 119.802 page views&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;http://www.finn.no/finn/realestate/homes/object?finnkode=48957077&quot;&gt;Beach property, Sandefjord, 45 million NOK&lt;/a&gt; – 106.544 page views&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;http://www.finn.no/finn/realestate/homes/object?finnkode=49150651&quot;&gt;House by the sea, Larvik, 45 million NOK&lt;/a&gt; – 105.591 page views&lt;/li&gt;
&lt;/ol&gt;

&lt;h2 id=&quot;most-popular-car-searches&quot;&gt;Most popular car searches&lt;/h2&gt;
&lt;ol&gt;
  &lt;li&gt;&lt;a href=&quot;http://m.finn.no/bap/forsale/search.html?q=hengerfeste&quot;&gt;hengerfeste&lt;/a&gt; – 867.023 searches&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;http://m.finn.no/bap/forsale/search.html?q=skinn&quot;&gt;skinn&lt;/a&gt; – 657.920 searches&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;http://m.finn.no/bap/forsale/search.html?q=innbytte&quot;&gt;innbytte&lt;/a&gt; – 604.242 searches&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;http://m.finn.no/bap/forsale/search.html?q=russebil&quot;&gt;russebil&lt;/a&gt; – 515.618 searches&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;http://m.finn.no/bap/forsale/search.html?q=amg&quot;&gt;amg&lt;/a&gt; – 511.311 searches&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;http://m.finn.no/bap/forsale/search.html?q=turbo&quot;&gt;turbo&lt;/a&gt; – 481.457 searches&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;http://m.finn.no/bap/forsale/search.html?q=golf&quot;&gt;golf&lt;/a&gt; – 424.004 searches&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;http://m.finn.no/bap/forsale/search.html?q=gti&quot;&gt;gti&lt;/a&gt; – 397.822 searches&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;http://m.finn.no/bap/forsale/search.html?q=bmw&quot;&gt;bmw&lt;/a&gt; – 386.346 searches&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;http://m.finn.no/bap/forsale/search.html?q=webasto&quot;&gt;webasto&lt;/a&gt; – 352.581 searches&lt;/li&gt;
&lt;/ol&gt;

&lt;h2 id=&quot;most-popular-car-ads&quot;&gt;Most popular car ads&lt;/h2&gt;
&lt;ol&gt;
  &lt;li&gt;&lt;a href=&quot;http://www.finn.no/finn/car/used/object?finnkode=48525725&quot;&gt;Audi A6&lt;/a&gt; – 515.192 page views (most seen ad in 2014).&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;http://www.finn.no/finn/car/used/object?finnkode=44945284&quot;&gt;Koenigsegg&lt;/a&gt; – 383.738 page views&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;http://www.finn.no/finn/car/used/object?finnkode=48233534&quot;&gt;Lamborghini&lt;/a&gt; – 222.780 page views&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;http://www.finn.no/finn/car/used/object?finnkode=49468662&quot;&gt;Land Rover&lt;/a&gt; – 176.949 page views&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;http://www.finn.no/finn/car/used/object?finnkode=45544161&quot;&gt;Aston Martin&lt;/a&gt; – 168.105 page views&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;http://www.finn.no/finn/car/used/object?finnkode=45771810&quot;&gt;Mercedes-Benz&lt;/a&gt; – 156.185 page views&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;http://www.finn.no/finn/car/used/object?finnkode=37119200&quot;&gt;Lamborghini&lt;/a&gt; – 130.370 page views&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;http://www.finn.no/finn/car/used/object?finnkode=48230879&quot;&gt;Volkswagen (1964)&lt;/a&gt; – 123.546 page views&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;http://www.finn.no/finn/car/used/object?finnkode=45426111&quot;&gt;Hummer&lt;/a&gt; – 119.714 page views&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;http://www.finn.no/finn/car/mobilehome/object?finnkode=46672573&quot;&gt;Scania motorhome&lt;/a&gt; – 114.481 page views&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href=&quot;http://www.vg.no/forbruker/dette-vil-vi-ha-paa-finn/a/23359786/&quot;&gt;VG also wrote about this&lt;/a&gt;.
Source: &lt;a href=&quot;http://www.finn.no&quot;&gt;FINN&lt;/a&gt; most popular searches and most seen ads in 2014 from selected marketplaces January 1st until December 17th.&lt;/p&gt;
</content>
        
        <author>
            <name>Andersos</name>
            
            <email></email>
            
        </author>
        
    </entry>
    
    <entry>
        <title>React in the Real World</title>
        <link rel="alternate" type="text/html" href="https://tech.finn.no2014/12/15/react-in-the-real-world/"/>
        <updated>2014-12-15T12:36:31+00:00</updated>
        <id>https://tech.finn.no/2014/12/15/react-in-the-real-world</id>
        <content type="html">&lt;p&gt;At &lt;a href=&quot;http://www.finn.no/oppdrag/&quot;&gt;FINN oppdrag&lt;/a&gt;, a year ago we came to realize that our front-end code, written in JSP/jQuery, had become so large and complex that it affected our ability to produce features. We spent more time making sure the DOM manipulations were correct and did not break, than actually creating value for our users.&lt;/p&gt;

&lt;p&gt;So we agreed that we needed a front-end framework to simplify our development and after looking into &lt;a href=&quot;http://emberjs.com/&quot;&gt;Ember&lt;/a&gt; and &lt;a href=&quot;https://angularjs.org/&quot;&gt;Angular&lt;/a&gt; we decided to go for &lt;a href=&quot;http://facebook.github.io/react/&quot;&gt;React&lt;/a&gt;, recently open sourced by Facebook.&lt;/p&gt;

&lt;p&gt;Our attraction to React was based on its promise of providing components with encapsulated state, fast automatic DOM manipulations, and the ability to render on the server.&lt;/p&gt;

&lt;h2 id=&quot;migration&quot;&gt;Migration&lt;/h2&gt;
&lt;p&gt;We started by porting some functionality that already existed, and our first discovery was that the lines of codes needed to make the exact same functionality was almost reduced by half. Secondly, the code became clearer and easier to understand. And thirdly, as a combination of the reduced lines of code, the cleaner code and no manual DOM manipulations, we got less bugs.&lt;/p&gt;

&lt;p&gt;Since then all new features have been written with React, and when we are requested to make modifications to functionality written in the old code, we rewrite them to use React.&lt;/p&gt;

&lt;p&gt;When migrating large pages to React we start from the inside, the deepest DOM nodes, and let it gradually grow till the whole page is reactified. This often happened through several iterations and deployments, as React and the old JSP/JQuery can quite peacefully coexist.&lt;/p&gt;

&lt;h2 id=&quot;bundles&quot;&gt;Bundles&lt;/h2&gt;
&lt;p&gt;With React, HTML is represented as JavaScripts, so the JavaScript bundles will contain all the HTML, and hence grow as the pages grow. Instead of having a bundle for each page (very small), or having a bundle for the whole site (very big), we found a middle way where we have bundles based on roles, in our use cases contractor and owner. A possible improvement on this is loading JavaScript on demand with technologies like &lt;a href=&quot;http://webpack.github.io/&quot;&gt;webpack&lt;/a&gt;.&lt;/p&gt;

&lt;h2 id=&quot;flux&quot;&gt;Flux&lt;/h2&gt;
&lt;p&gt;So if React is our first great discovery, the Flux pattern is very much in second place. &lt;a href=&quot;http://facebook.github.io/flux/docs/overview.html&quot;&gt;The Flux pattern&lt;/a&gt; decouples the logic from the views, in a different way than the traditional MVC-pattern. Logic and state are located in stores, and actions from the views to stores are asynchronous. When state in stores change, they will update React components that listen to changes, providing a single source of truth and unidirectional flow of data.&lt;/p&gt;

&lt;h2 id=&quot;server-side-rendering&quot;&gt;Server-side rendering&lt;/h2&gt;
&lt;p&gt;Our next step, which is yet to be solved, is server-side rendering. Server-side rendering means having the server generate and return the whole HTML page to the browser, instead of having the browser render HTML after downloading the JavaScript. This is especially important for SEO (search engine optimization) and mobile users, as search engines rarely run JavaScript, and mobile users have low bandwidth hence will take longer time before the page shows. However, having the server return a fully rendered HTML to browser is in general good for perceived page load performance.&lt;/p&gt;

&lt;p&gt;Server-side rendering requires running JavaScript on the server, and that is straightforward with Node. However it is not that straightforward in our Java/Tomcat/Spring backend stack. We envision three possible solutions for this problem:
1)	Running JavaScript on the JVM with the Nashorn JavaScript engine.
2)	Delegating execution of JavaScript to an external process running Node.
3)	Having Node run as smart-proxy in front of Java/Tomcat.&lt;/p&gt;

&lt;p&gt;All these three solutions have their own benefits and challenges.&lt;/p&gt;

&lt;p&gt;The first solution, running JavaScript in Java, has the benefits of close integration with the controller layer written in Java, and possibly making deployment easier, as the Java environment already has access to the JavaScript bundles. The downside of this approach seems to be performance, as the Nashorn JavaScript engine compiles and optimizes the JavaScript during the first runs, hence uses a long time to «warm up».  So unless we write a mechanism that warms up the code, the first pages will be very slow to load.&lt;/p&gt;

&lt;p&gt;The second solution, which involves interacting with an external process running Node, has the benefits that JavaScript runs natively and very fast. The downsides are more complicated deployment process where the Node instance needs access to the JavaScript bundles, and the overhead of interacting with an external process, possible through HTTP or Thrift, can impact performance negatively.&lt;/p&gt;

&lt;p&gt;The third solution, where Node is placed in front Tomcat, and works as a &lt;a href=&quot;http://www.feedhenry.com/transforming-enterprise-node-js-feedhenry/&quot;&gt;smart-proxy&lt;/a&gt;, only enhancing certain pages, while simply forwarding the rest, has the benefits of executing JavaScript natively. While the downsides are more complicated deployment, and the possible complex process of executing certain JavaScript code found in HTML, and updating the HTML with the executed JavaScript, before it is served to the browser.&lt;/p&gt;

&lt;h2 id=&quot;conclusion&quot;&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;So, apart from server-side rendering, which we are working on, our transition to React has been smooth and a great success. It has improved our codebase, made developers happy, and most importantly, improved our speed of delivering features, which in the end is the most important thing.&lt;/p&gt;

&lt;p&gt;Tor Arne Kvaløy
Senior developer&lt;/p&gt;
</content>
        
        <author>
            <name>Tor Arne Kvaløy</name>
            
            <email></email>
            
        </author>
        
    </entry>
    
    <entry>
        <title>Nova Hackathon</title>
        <link rel="alternate" type="text/html" href="https://tech.finn.no2014/12/11/Nova-Hackathon/"/>
        <updated>2014-12-11T17:00:00+00:00</updated>
        <id>https://tech.finn.no/2014/12/11/Nova-Hackathon</id>
        <content type="html">&lt;p&gt;We were invited by &lt;a href=&quot;http://www.nova100.no/&quot;&gt;Nova&lt;/a&gt; for their first Nova Hackathon.&lt;/p&gt;

&lt;p&gt;A Hackathon is period of time where computer programmers collaborate intensively on a given task or multiple tasks. For the Nova Hackathon Challenge Finn was invited to make a task for the students along with 2 other companies.&lt;/p&gt;

&lt;p&gt;The challenge that Finn gave the students was to setup a simple web-server. It was based on &lt;a href=&quot;https://github.com/rchatley/extreme_startup&quot;&gt;Extreme startup&lt;/a&gt; by &lt;a href=&quot;https://github.com/rchatley&quot;&gt;Robert Chatley&lt;/a&gt;. This software supports a workshop where teams can compete to build a software product that satisfies market demand.&lt;/p&gt;

&lt;p&gt;We helped the students to setup their web-servers, each in their own respective language of choice. Their web-server’s url had to be registered on the Finn server. Once this was setup correctly, the Finn server would start to send http requests to their web-server. These http requests were various questions, the students had to get their web-server to respond to these requests not knowing the questions in advance, nor how they would be scored. The only thing they knew was that they would get a http request from the FINN server and had to respond to it. The questions were sent at a quick pace and were constantly evolving. This meant that their web-server’s code also was quickly evolving, a condensed real-life example of team-based agile programming.&lt;/p&gt;

&lt;p&gt;Avanade’s task was to build an application for retrieving relevant data for a transport company. The students would then setup an prototype and a suggestion for further improvements. Itera gave the students a task centered around a calculator for mortgages, with focus on front-end javascript. It has to be mentioned that HP was so kind to borrow their offices for the Hackathon event. Read what HP has written about the &lt;a href=&quot;http://h30499.www3.hp.com/t5/Garasjen-uten-nerden-stopper/HP-legger-ut-r%C3%B8d-l%C3%B8per-for-IT-studentene/ba-p/6675928#.VIlQC6SG-lb&quot;&gt;Hackaton event&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The students was divided into 4 groups with different backgrounds. The business representatives helped the students underway and as you can see by this picture&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/2014-12-11-nova-hackathon/hackaton.JPG&quot; alt=&quot;Participants for the Finn's Challenge&quot; title=&quot;Participants for the Finn's Challenge&quot; /&gt;&lt;/p&gt;

&lt;p&gt;the students was highely focused and competitive against the other groups.&lt;/p&gt;

&lt;p&gt;Feedback from Nova was that no company in any of their events had ever been so involved throughout the whole day with the students as we had been. We had heaps of fun together and the extreme startup competition was a fantastic suggestion, hands-on and exciting. We had one team who implemented it in php and finished all 8 rounds in under 2 hours including questions that involved html scraping finn.no to find titles and prices for random finnkode! It was fun to see all the hard work and that the students put a tremendous effort in the challenge. The Finn ambassadors, &lt;a href=&quot;https://twitter.com/andersos&quot;&gt;Anders Olsen Sandvik&lt;/a&gt; , &lt;a href=&quot;https://twitter.com/mck_sw&quot;&gt;Mick Semb Sever&lt;/a&gt;, &lt;a href=&quot;https://no.linkedin.com/pub/nicolai-h%C3%B8ge/2/333/524&quot;&gt;Nicolai Høge&lt;/a&gt;, &lt;a href=&quot;https://twitter.com/sverreroos&quot;&gt;Sverre Roos&lt;/a&gt; and &lt;a href=&quot;https://twitter.com/nadorhingsten&quot;&gt;Rida Aatif&lt;/a&gt; had a lot of fun collaborating with the students and we can agree that this is something we would definitely do again.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/2014-12-11-nova-hackathon/participants.jpg&quot; alt=&quot;Picture of all the Participants&quot; title=&quot;Picture of all the Participant&quot; /&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/2014-12-11-nova-hackathon/php_group.jpg&quot; alt=&quot;Group 2&quot; title=&quot;Group 2&quot; /&gt;&lt;/p&gt;
</content>
        
        <author>
            <name>firiaati</name>
            
            <email></email>
            
        </author>
        
    </entry>
    
    <entry>
        <title>FINN techday 2014</title>
        <link rel="alternate" type="text/html" href="https://tech.finn.no2014/11/26/finn-techday-2014/"/>
        <updated>2014-11-26T08:55:00+00:00</updated>
        <id>https://tech.finn.no/2014/11/26/finn-techday-2014</id>
        <content type="html">&lt;p&gt;One of the ways of getting inspired and educated about technology here at FINN is through our “Tech-dag”.
This year we have a packed program which consists of workshops, some talks and a bunch of lightning talks.
Last time we posted about &lt;a href=&quot;http://tech.finn.no/2012/06/12/technology-day-2012/&quot;&gt;techday was in 2012&lt;/a&gt;.&lt;/p&gt;

&lt;h1 id=&quot;presentations&quot;&gt;Presentations&lt;/h1&gt;

&lt;p&gt;Throughout the day we had some long presentations and some lightning talks.
Lightning talks are 10 minutes long and tightly timed.&lt;br /&gt;
The topics are broad ranging, and not limited to programming at all.&lt;/p&gt;

&lt;blockquote class=&quot;twitter-tweet&quot; data-cards=&quot;hidden&quot; lang=&quot;en&quot;&gt;&lt;p&gt;Starting &lt;a href=&quot;https://twitter.com/hashtag/techdag2014?src=hash&quot;&gt;#techdag2014&lt;/a&gt; with &lt;a href=&quot;https://twitter.com/sisomm&quot;&gt;@sisomm&lt;/a&gt; and the Internet of things &lt;a href=&quot;https://twitter.com/hashtag/FINN_no?src=hash&quot;&gt;#FINN_no&lt;/a&gt; &lt;a href=&quot;http://t.co/Xdx3HOMY3q&quot;&gt;pic.twitter.com/Xdx3HOMY3q&lt;/a&gt;&lt;/p&gt;&amp;mdash; Anders Olsen Sandvik (@Andersos) &lt;a href=&quot;https://twitter.com/Andersos/status/537540778328072193&quot;&gt;November 26, 2014&lt;/a&gt;&lt;/blockquote&gt;

&lt;p&gt;Here is the list of speakers and their slides:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://twitter.com/sisomm&quot;&gt;Simen Sommerfeldt&lt;/a&gt; - &lt;a href=&quot;http://goo.gl/5It5LL&quot;&gt;Internet of Things&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Rian Liebenberg&lt;/li&gt;
&lt;li&gt;William Daraman, VG Tactus	- &lt;a href=&quot;http://goo.gl/5kjRkU&quot;&gt;&quot;Egg og iBeacon&quot;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Stefan Engelien - &lt;a href=&quot;http://www.slideshare.net/StefanAndreasEngelie/eksperimentene-som-skapte-finn-smjobber&quot;&gt;Eksperimentene som skapte FINN Småjobber&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Geir Pettersen - &lt;a href=&quot;http://goo.gl/CgoCux&quot;&gt;Hospitering&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://twitter.com/spjelkavik&quot;&gt;Henning Spjelkavik&lt;/a&gt; - &lt;a href=&quot;http://goo.gl/4VCCQY&quot;&gt;Strategisk planlegging med 'impact mapping'&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://twitter.com/ASvanevik&quot;&gt;Alexander Svanevik&lt;/a&gt; - &lt;a href=&quot;http://goo.gl/5jJ0r4&quot;&gt;Language Hacking&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://twitter.com/twidero&quot;&gt;Tom Widerøe&lt;/a&gt; - &lt;a href=&quot;http://goo.gl/KpkX9k&quot;&gt;Universell utforming – hva har blitt bedre&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.twitter.com/pjwalstrom&quot;&gt;Per Jørgen Walstrøm&lt;/a&gt; - &lt;a href=&quot;https://goo.gl/QVym94&quot;&gt;Nummi - om hockey og biler&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Mikael Lindström - &lt;a href=&quot;http://goo.gl/ulvSlJ&quot;&gt;SPiD in the cloud&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://twitter.com/andersos&quot;&gt;Anders Olsen Sandvik&lt;/a&gt; - &lt;a href=&quot;http://goo.gl/3kp2gV&quot;&gt;Isomorphic Apps&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://twitter.com/leftieFriele&quot;&gt;Espen Dalløkken&lt;/a&gt; - a talk based on the blog post:&lt;a href=&quot;http://goo.gl/Nuqm4T&quot;&gt;I was wrong&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://twitter.com/tawaterhouse&quot;&gt;Torgeir Waterhouse&lt;/a&gt; - &lt;a href=&quot;http://goo.gl/ikcAYS&quot;&gt;hei FINN.no - hva skjer, internett og sånt...&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;

&lt;h1 id=&quot;arduino-workshop&quot;&gt;Arduino Workshop&lt;/h1&gt;

&lt;p&gt;For this workshop we were all split out into teams and each team got a &lt;a href=&quot;http://arduino.cc/en/Main/ArduinoStarterKit&quot;&gt;Arduino starter kit&lt;/a&gt;. The task was to make something cool. Check out this video from what group 10 did:&lt;/p&gt;

&lt;iframe width=&quot;560&quot; height=&quot;315&quot; src=&quot;//www.youtube.com/embed/SbnQW7Kl648&quot; frameborder=&quot;0&quot; allowfullscreen=&quot;&quot;&gt;&lt;/iframe&gt;

&lt;blockquote class=&quot;twitter-tweet&quot; lang=&quot;en&quot;&gt;&lt;p&gt;50 minutes of work. &lt;a href=&quot;https://twitter.com/hashtag/techdag2014?src=hash&quot;&gt;#techdag2014&lt;/a&gt; &lt;a href=&quot;http://t.co/lbByxa7iJb&quot;&gt;pic.twitter.com/lbByxa7iJb&lt;/a&gt;&lt;/p&gt;&amp;mdash; Christopher Kolstad (@chriswk) &lt;a href=&quot;https://twitter.com/chriswk/status/537559324982509568&quot;&gt;November 26, 2014&lt;/a&gt;&lt;/blockquote&gt;

&lt;blockquote class=&quot;twitter-tweet&quot; lang=&quot;en&quot;&gt;&lt;p&gt;&lt;a href=&quot;https://twitter.com/hashtag/techdag2014?src=hash&quot;&gt;#techdag2014&lt;/a&gt; &lt;a href=&quot;http://t.co/J6qYJOrZBt&quot;&gt;pic.twitter.com/J6qYJOrZBt&lt;/a&gt;&lt;/p&gt;&amp;mdash; PJ Walstroem (@pjwalstrom) &lt;a href=&quot;https://twitter.com/pjwalstrom/status/537551518179024896&quot;&gt;November 26, 2014&lt;/a&gt;&lt;/blockquote&gt;

&lt;blockquote class=&quot;twitter-tweet&quot; lang=&quot;en&quot;&gt;&lt;p&gt;&lt;a href=&quot;https://twitter.com/hashtag/techdag2014?src=hash&quot;&gt;#techdag2014&lt;/a&gt; &lt;a href=&quot;http://t.co/H6uINBBsoH&quot;&gt;pic.twitter.com/H6uINBBsoH&lt;/a&gt;&lt;/p&gt;&amp;mdash; Sissel Sveum (@SisselSveum) &lt;a href=&quot;https://twitter.com/SisselSveum/status/537538347087187968&quot;&gt;November 26, 2014&lt;/a&gt;&lt;/blockquote&gt;
&lt;p&gt;&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;h1 id=&quot;official-hashtag-techdag2014&quot;&gt;Official hashtag &lt;a href=&quot;https://twitter.com/search?f=realtime&amp;amp;q=%23techdag2014&amp;amp;src=typd&quot;&gt;#techdag2014&lt;/a&gt;&lt;/h1&gt;
</content>
        
        <author>
            <name>Andersos</name>
            
            <email></email>
            
        </author>
        
    </entry>
    
    <entry>
        <title>ApacheCon Budapest 2014</title>
        <link rel="alternate" type="text/html" href="https://tech.finn.no2014/11/25/apachecon-budapest/"/>
        <updated>2014-11-25T13:00:00+00:00</updated>
        <id>https://tech.finn.no/2014/11/25/apachecon-budapest</id>
        <content type="html">&lt;p&gt;Last week was Europe’s &lt;a href=&quot;http://drbacchus.com/apachecon-budapest-2014/&quot;&gt;ApacheCon&lt;/a&gt;, held in Budapest.&lt;/p&gt;

&lt;p&gt;A refreshingly “&lt;a href=&quot;http://iamfortress.wordpress.com/2014/11/23/a-newcomers-perspective-on-apachecon-europe-2014/&quot; title=&quot;Shawn McKinney&quot;&gt;laid-back and no-nonsense&lt;/a&gt;” conference free from the douchebaggery that comes from big sponsors and marketing. This year it was held in the awesome 5-star Corinthia Hotel, and consisted of a welcoming and cheerful vibe, a place for apache members, committers, and all, to get together and better know each other. Apache continues to grow as the foundation for strong communities building trusted open sourced solutions, for running half the internet, and providing a substantial amount of code to your java stack.&lt;/p&gt;

&lt;p&gt;Some hot topics were Docker, Mesos, Spark, Cassandra, CouchDB, Hadoop, Solr, OpenOffice, and the need for greater diversity in our communities.&lt;/p&gt;

&lt;p&gt;Spark is all the rage because of its brevity and simplicity, but isn’t really a complete solution yet  because it doesn’t scale in many situations.&lt;/p&gt;

&lt;p&gt;Docker is awesome, and looks to take over the testing domain, but it remains limited and out of production as long as it has no network stack implementation. There was a very cool demonstration of Mesos and Aurora increasing production utilisation, even allowing in quiet periods for development/testing servers to come in.&lt;/p&gt;

&lt;p&gt;The next release of YARN (2.6) will see support for long-lived services, whereby we can transform many of our hadoop jobs with very little effort into true streaming solutions. YARN will also be able to &lt;a href=&quot;http://blog.sequenceiq.com/blog/2014/11/20/yarn-containers-and-docker&quot;&gt;deploy docker containers&lt;/a&gt; through its clusters, this could be a very nice solution for our batch jobs. Putting these together and you see that the technical separation between streaming and aggregating solutions fades away and it really just boils down to what data structures you solve each use case with. Talking with a hadoop committer it came to light that FINN could be running the fastest hadoop cluster that they know of, given HDFS runs on SSDs and is dedicated just for hadoop internals.&lt;/p&gt;

&lt;p&gt;Of no surprise Solr dealt a lot with scaling and &lt;a href=&quot;http://events.linuxfoundation.org/sites/events/files/slides/HighPerformanceSolr.pdf&quot;&gt;performance&lt;/a&gt;, while Cassandra presented use-cases from across the industry. It felt that big tech companies are migrating their databases over to Cassandra not just for availability but often simply for the sake of performance. “&lt;a href=&quot;http://www.slideshare.net/planetcassandra/cassandra-summit-2014-deploying-cassandra-for-call-of-duty&quot;&gt;Call of Duty&lt;/a&gt;” was rewritten from mysql to cassandra, resulting in the 95th percentile for requests going down to under 500μs. You don’t need complicated cache strategies when your backend is this fast!&lt;/p&gt;

&lt;p&gt;Other Cassandra talks included Eric Evans presenting &lt;a href=&quot;http://opennms.github.com/newts&quot;&gt;Newts&lt;/a&gt;, an lazy-aggregating time-series solution built with graphing and lucene search inbuilt. And &lt;a href=&quot;http://events.linuxfoundation.org/sites/events/files/slides/Intro-To-Usergrid%20-%20ApacheCon%20EU%202014.pdf&quot;&gt;Apache UserGrid&lt;/a&gt;, a fascinating project built, it is your LAMP stack, a BaaS framework, for the mobile-first world.&lt;/p&gt;

&lt;p&gt;Patrick McFadin held a tutorial putting together kafka, cassandra, and spark, to create a very &lt;a href=&quot;https://github.com/killrweather/killrweather&quot;&gt;elegant scaling streaming solution&lt;/a&gt;. Patrick also discussed the performance gain of putting spark RDD data in cassandra rather than using files on disk. Chatting with both Eric Evans (he who coined “NoSQL”) and Patrick highlighted that the throughput per node of our Cassandra cluster is leading in the industry, it’s just to add more servers and reap the benefits.&lt;/p&gt;

&lt;p&gt;And yours humbly presented to a packed room our &lt;a href=&quot;https://prezi.com/xgjrvkxhxkg8/apachecon-cassandra-and-hadoop-finnno/&quot;&gt;Cassandra and Hadoop&lt;/a&gt; use-cases. Making mention to the need for better gender balance within Apache, Schibsted’s role in online classified markets across Europe, the need to look beyond current trends in our industry when designing systems, and having a healthy preference towards AP systems in the enterprise.&lt;/p&gt;

&lt;p&gt;Absolutely a conference I’d recommend to infrastructure, operations, and back-enders serious about their technologies.&lt;/p&gt;

&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;table&gt;&lt;tr&gt;&lt;td&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;p class=&quot;centerify&quot;&gt;Oh… and Happy &lt;a href=&quot;https://blogs.apache.org/foundation/entry/the_apache_software_foundation_celebrates2&quot;&gt;15th birthday&lt;/a&gt; Apache!&lt;br /&gt;&lt;img src=&quot;http://farm8.staticflickr.com/7533/15816582111_4b19b886c5_c.jpg&quot; alt=&quot;Party&quot; /&gt;&lt;br /&gt;
More photos from the conference &lt;a href=&quot;http://events.linuxfoundation.org/events/apachecon-europe&quot;&gt;here&lt;/a&gt;&lt;/p&gt;

</content>
        
        <author>
            <name>mick</name>
            
            <email></email>
            
        </author>
        
    </entry>
    
    <entry>
        <title>Email providers in Norway</title>
        <link rel="alternate" type="text/html" href="https://tech.finn.no2014/11/21/email-providers-in-norway/"/>
        <updated>2014-11-21T18:00:00+00:00</updated>
        <id>https://tech.finn.no/2014/11/21/email-providers-in-norway</id>
        <content type="html">&lt;p&gt;It all started with a simple tweet:&lt;/p&gt;

&lt;blockquote class=&quot;twitter-tweet&quot; lang=&quot;en&quot;&gt;&lt;p&gt;Er det noen som har noe norsk statistikk på bruk av e-post (gmail vs. outlook vs. online vs. yahoo osv)? F.eks &lt;a href=&quot;https://twitter.com/labs_finn_no&quot;&gt;@labs_finn_no&lt;/a&gt; eller &lt;a href=&quot;https://twitter.com/NRKbeta&quot;&gt;@NRKbeta&lt;/a&gt;?&lt;/p&gt;&amp;mdash; Håvard Bergersen (@haavardb) &lt;a href=&quot;https://twitter.com/haavardb/status/441921895643226112&quot;&gt;March 7, 2014&lt;/a&gt;&lt;/blockquote&gt;

&lt;p&gt;“Does anyone have any Norwegian statistics on the use of email (Gmail vs. Outlook vs. Online vs. Yahoo etc.)? Eg &lt;a href=&quot;https://twitter.com/labs_finn_no&quot;&gt;@labs_finn_no&lt;/a&gt; or &lt;a href=&quot;https://twitter.com/NRKbeta&quot;&gt;@nrkbeta&lt;/a&gt;.”&lt;/p&gt;

&lt;p&gt;We have access to the data so if this will help someone why not post it.
We have around 4 million email addresses in a database. These emails are not verified. The data was retrieved &lt;time datetime=&quot;2014-06-06&quot;&gt;2014-06-06&lt;/time&gt;.
To answer the question “Gmail vs. Outlook vs. Online vs. Yahoo etc.” we would say:&lt;/p&gt;

&lt;p&gt;Microsoft 33,57%&lt;br /&gt;
Google 15,32%&lt;br /&gt;
Telenor 12,53%&lt;br /&gt;
Yahoo 4,31%&lt;/p&gt;

&lt;p&gt;The lists under shows the domain, number registered and percent of total.
At the end is the table of the more popular email service providers (this list is cut off at 2500). Watch this space in the future if this is information that interests you.&lt;/p&gt;

&lt;h2 id=&quot;popular-services&quot;&gt;Popular services:&lt;/h2&gt;
&lt;p&gt;Microsoft 33,57% (sum of hotmail, live, msn and outlook)&lt;br /&gt;
hotmail.com (1106084 - 28.06%)&lt;br /&gt;
live.no (96052 - 2.44%)&lt;br /&gt;
hotmail.no (67602 - 1.71%)&lt;br /&gt;
msn.com (29675 - 0.75%)&lt;br /&gt;
live.com (15014 - 0.38%)&lt;br /&gt;
outlook.com (9142 - 0.23%)&lt;/p&gt;

&lt;p&gt;Google 15,32% (sum of gmail and googlemail)&lt;br /&gt;
gmail.com (599293 - 15.20%)&lt;br /&gt;
gmail.no (2982 - 0.08%)&lt;br /&gt;
googlemail.com (1766 - 0.04%)&lt;/p&gt;

&lt;p&gt;Telenor 12,53% (sum og online, frisurf, telenor and adsl)&lt;br /&gt;
online.no (444897 - 11.28%)&lt;br /&gt;
frisurf.no (41768 - 1.06%)&lt;br /&gt;
telenor.com (3903 - 0.10%)&lt;br /&gt;
adsl.no (3643 - 0.09%)&lt;/p&gt;

&lt;p&gt;Yahoo 4,31%&lt;br /&gt;
yahoo.no (94029 - 2.39%)&lt;br /&gt;
yahoo.com (75772 - 1.92%)&lt;/p&gt;

&lt;h2 id=&quot;universities&quot;&gt;Universities:&lt;/h2&gt;
&lt;p&gt;stud.ntnu.no (7688 - 0.20%)&lt;br /&gt;
ntnu.no (1421 - 0.04%)&lt;br /&gt;
stud.nhh.no (1008 - 0.03%)&lt;br /&gt;
student.bi.no (925 - 0.02%)&lt;br /&gt;
hiof.no (921 - 0.02%)&lt;br /&gt;
medisin.uio.no (820 - 0.02%)&lt;br /&gt;
student.sv.uio.no (746 - 0.02%)&lt;br /&gt;
ifi.uio.no (725 - 0.02%)&lt;br /&gt;
uit.no (666 - 0.02%)&lt;br /&gt;
stud.hib.no (557 - 0.01%)&lt;br /&gt;
studmed.uio.no (522 - 0.01%)&lt;br /&gt;
umb.no (411 - 0.01%)&lt;br /&gt;
uus.no (408 - 0.01%)&lt;br /&gt;
uis.no (407 - 0.01%)&lt;/p&gt;

&lt;h2 id=&quot;telcos&quot;&gt;&lt;a href=&quot;http://en.wikipedia.org/wiki/Telephone_company&quot;&gt;Telcos&lt;/a&gt;:&lt;/h2&gt;
&lt;p&gt;online.no (444897 - 11.28%)&lt;br /&gt;
frisurf.no (41768 - 1.06%)&lt;br /&gt;
telenor.com (3903 - 0.10%)&lt;br /&gt;
adsl.no (3643 - 0.09%)&lt;/p&gt;

&lt;p&gt;c2i.net (67080 - 1.70%)&lt;br /&gt;
broadpark.no (48232 - 1.22%)&lt;br /&gt;
lyse.net (25964 - 0.66%)&lt;br /&gt;
getmail.no (19497 - 0.49%)&lt;br /&gt;
chello.no (18485 - 0.47%)&lt;br /&gt;
tele2.no (12218 - 0.31%)&lt;br /&gt;
bluezone.no (10187 - 0.26%)&lt;br /&gt;
netcom.no (7987 - 0.20%)&lt;br /&gt;
sensewave.com (7635 - 0.19%)&lt;br /&gt;
ntebb.no (6122 - 0.16%)&lt;/p&gt;

&lt;h2 id=&quot;kommune&quot;&gt;&lt;a href=&quot;http://no.wikipedia.org/wiki/Kommune&quot;&gt;Kommune&lt;/a&gt;:&lt;/h2&gt;
&lt;p&gt;trondheim.kommune.no (1203 - 0.03%)&lt;br /&gt;
bergen.kommune.no (1156 - 0.03%)&lt;br /&gt;
baerum.kommune.no (906 - 0.02%)&lt;br /&gt;
kristiansand.kommune.no (688 - 0.02%)&lt;br /&gt;
asker.kommune.no (557 - 0.01%)&lt;br /&gt;
stavanger.kommune.no (500 - 0.01%)&lt;br /&gt;
fredrikstad.kommune.no (433 - 0.01%)&lt;br /&gt;
sandnes.kommune.no (420 - 0.01%)&lt;/p&gt;

&lt;h2 id=&quot;some-random&quot;&gt;Some random:&lt;/h2&gt;
&lt;p&gt;mil.no (2231 - 0.06%)&lt;br /&gt;
nrk.no (1942 - 0.05%)&lt;br /&gt;
finn.no (1731 - 0.04%)&lt;br /&gt;
vegvesen.no (1700 - 0.04%)&lt;br /&gt;
posten.no (1566 - 0.04%)&lt;br /&gt;
politiet.no (1549 - 0.04%)&lt;br /&gt;
operamail.com (1511 - 0.04%)&lt;br /&gt;
sas.no (1324 - 0.03%)&lt;br /&gt;
vg.no (656 - 0.02%)&lt;/p&gt;

&lt;table&gt;
&lt;tr&gt;&lt;th&gt;Domain&lt;/th&gt;&lt;th&gt;Number registered&lt;/th&gt;&lt;th&gt;Percent of total&lt;/th&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;hotmail.com&lt;/td&gt;&lt;td&gt;1106084&lt;/td&gt;&lt;td&gt;28.06%&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;gmail.com&lt;/td&gt;&lt;td&gt;599293&lt;/td&gt;&lt;td&gt;15.20%&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;online.no&lt;/td&gt;&lt;td&gt;444897&lt;/td&gt;&lt;td&gt;11.28%&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;live.no&lt;/td&gt;&lt;td&gt;96052&lt;/td&gt;&lt;td&gt;2.44%&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;yahoo.no&lt;/td&gt;&lt;td&gt;94029&lt;/td&gt;&lt;td&gt;2.39%&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;yahoo.com&lt;/td&gt;&lt;td&gt;75772&lt;/td&gt;&lt;td&gt;1.92%&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;hotmail.no&lt;/td&gt;&lt;td&gt;67602&lt;/td&gt;&lt;td&gt;1.71%&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;c2i.net&lt;/td&gt;&lt;td&gt;67080&lt;/td&gt;&lt;td&gt;1.70%&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;broadpark.no&lt;/td&gt;&lt;td&gt;48232&lt;/td&gt;&lt;td&gt;1.22%&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;frisurf.no&lt;/td&gt;&lt;td&gt;41768&lt;/td&gt;&lt;td&gt;1.06%&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;start.no&lt;/td&gt;&lt;td&gt;33409&lt;/td&gt;&lt;td&gt;0.85%&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;msn.com&lt;/td&gt;&lt;td&gt;29675&lt;/td&gt;&lt;td&gt;0.75%&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;lyse.net&lt;/td&gt;&lt;td&gt;25964&lt;/td&gt;&lt;td&gt;0.66%&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;getmail.no&lt;/td&gt;&lt;td&gt;19497&lt;/td&gt;&lt;td&gt;0.49%&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;chello.no&lt;/td&gt;&lt;td&gt;18485&lt;/td&gt;&lt;td&gt;0.47%&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;live.com&lt;/td&gt;&lt;td&gt;15014&lt;/td&gt;&lt;td&gt;0.38%&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;me.com&lt;/td&gt;&lt;td&gt;12344&lt;/td&gt;&lt;td&gt;0.31%&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;tele2.no&lt;/td&gt;&lt;td&gt;12218&lt;/td&gt;&lt;td&gt;0.31%&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;wp.pl&lt;/td&gt;&lt;td&gt;10883&lt;/td&gt;&lt;td&gt;0.28%&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;bluezone.no&lt;/td&gt;&lt;td&gt;10187&lt;/td&gt;&lt;td&gt;0.26%&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;outlook.com&lt;/td&gt;&lt;td&gt;9142&lt;/td&gt;&lt;td&gt;0.23%&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;netcom.no&lt;/td&gt;&lt;td&gt;7987&lt;/td&gt;&lt;td&gt;0.20%&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;stud.ntnu.no&lt;/td&gt;&lt;td&gt;7688&lt;/td&gt;&lt;td&gt;0.20%&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;sensewave.com&lt;/td&gt;&lt;td&gt;7635&lt;/td&gt;&lt;td&gt;0.19%&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;tiscali.no&lt;/td&gt;&lt;td&gt;7583&lt;/td&gt;&lt;td&gt;0.19%&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;spray.no&lt;/td&gt;&lt;td&gt;7044&lt;/td&gt;&lt;td&gt;0.18%&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;bbnett.no&lt;/td&gt;&lt;td&gt;6887&lt;/td&gt;&lt;td&gt;0.17%&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;ntebb.no&lt;/td&gt;&lt;td&gt;6122&lt;/td&gt;&lt;td&gt;0.16%&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;epost.no&lt;/td&gt;&lt;td&gt;5975&lt;/td&gt;&lt;td&gt;0.15%&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;statoil.com&lt;/td&gt;&lt;td&gt;5835&lt;/td&gt;&lt;td&gt;0.15%&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;live.se&lt;/td&gt;&lt;td&gt;5273&lt;/td&gt;&lt;td&gt;0.13%&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;telia.com&lt;/td&gt;&lt;td&gt;4879&lt;/td&gt;&lt;td&gt;0.12%&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;mac.com&lt;/td&gt;&lt;td&gt;4588&lt;/td&gt;&lt;td&gt;0.12%&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;icloud.com&lt;/td&gt;&lt;td&gt;4532&lt;/td&gt;&lt;td&gt;0.11%&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;ebnett.no&lt;/td&gt;&lt;td&gt;4329&lt;/td&gt;&lt;td&gt;0.11%&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;haugnett.no&lt;/td&gt;&lt;td&gt;4294&lt;/td&gt;&lt;td&gt;0.11%&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;mail.ru&lt;/td&gt;&lt;td&gt;4091&lt;/td&gt;&lt;td&gt;0.10%&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;losmail.no&lt;/td&gt;&lt;td&gt;4069&lt;/td&gt;&lt;td&gt;0.10%&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;mimer.no&lt;/td&gt;&lt;td&gt;3995&lt;/td&gt;&lt;td&gt;0.10%&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;telenor.com&lt;/td&gt;&lt;td&gt;3903&lt;/td&gt;&lt;td&gt;0.10%&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;mail.com&lt;/td&gt;&lt;td&gt;3874&lt;/td&gt;&lt;td&gt;0.10%&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;adsl.no&lt;/td&gt;&lt;td&gt;3643&lt;/td&gt;&lt;td&gt;0.09%&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;o2.pl&lt;/td&gt;&lt;td&gt;3587&lt;/td&gt;&lt;td&gt;0.09%&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;yahoo.co.uk&lt;/td&gt;&lt;td&gt;3572&lt;/td&gt;&lt;td&gt;0.09%&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;inbox.lv&lt;/td&gt;&lt;td&gt;3348&lt;/td&gt;&lt;td&gt;0.08%&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;hjemme.no&lt;/td&gt;&lt;td&gt;3185&lt;/td&gt;&lt;td&gt;0.08%&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;halden.net&lt;/td&gt;&lt;td&gt;3021&lt;/td&gt;&lt;td&gt;0.08%&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;vikenfiber.no&lt;/td&gt;&lt;td&gt;3005&lt;/td&gt;&lt;td&gt;0.08%&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;gmail.no&lt;/td&gt;&lt;td&gt;2982&lt;/td&gt;&lt;td&gt;0.08%&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;student.uib.no&lt;/td&gt;&lt;td&gt;2953&lt;/td&gt;&lt;td&gt;0.07%&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;ymail.com&lt;/td&gt;&lt;td&gt;2901&lt;/td&gt;&lt;td&gt;0.07%&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;hydro.com&lt;/td&gt;&lt;td&gt;2852&lt;/td&gt;&lt;td&gt;0.07%&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;sf-nett.no&lt;/td&gt;&lt;td&gt;2689&lt;/td&gt;&lt;td&gt;0.07%&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;aol.com&lt;/td&gt;&lt;td&gt;2645&lt;/td&gt;&lt;td&gt;0.07%&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;interia.pl&lt;/td&gt;&lt;td&gt;2589&lt;/td&gt;&lt;td&gt;0.07%&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;web.de&lt;/td&gt;&lt;td&gt;2582&lt;/td&gt;&lt;td&gt;0.07%&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;loqal.no&lt;/td&gt;&lt;td&gt;2566&lt;/td&gt;&lt;td&gt;0.07%&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;yahoo.se&lt;/td&gt;&lt;td&gt;2517&lt;/td&gt;&lt;td&gt;0.06%&lt;/td&gt;&lt;/tr&gt;
&lt;/table&gt;
</content>
        
        <author>
            <name>Andersos</name>
            
            <email></email>
            
        </author>
        
    </entry>
    
    <entry>
        <title>Workshop with blindfolded lunch</title>
        <link rel="alternate" type="text/html" href="https://tech.finn.no2014/11/19/workshop-with-blindfolded-lunch/"/>
        <updated>2014-11-19T18:00:00+00:00</updated>
        <id>https://tech.finn.no/2014/11/19/workshop-with-blindfolded-lunch</id>
        <content type="html">&lt;p&gt;Workshop with blindfolded lunch in cooperation with &lt;a href=&quot;https://www.blindeforbundet.no&quot;&gt;The Norwegian Association of the Blind and Partially Sighted (NABP)&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;At FINN a grassroots group of developers and interaction designers launched an initiative to bring up awareness of accessibility. The law of accessibility was introduced July 1st 2014 and m.finn.no is covered by that law because of its major design changes. The Accessibility group in FINN wanted to convey the same message as the NABP; caring for people with disabilities when we build our services, hoping it lead to better user experiences for all.&lt;/p&gt;

&lt;p&gt;Wednesday November 19th, FINN arranged an accessibility workshop in cooperation with NABP. About 30 developers, designers and product owners attended the keynote by &lt;a href=&quot;https://twitter.com/skotkjerra&quot;&gt;Stein Erik Skotkjerra&lt;/a&gt; who is blind and by &lt;a href=&quot;https://twitter.com/krilium&quot;&gt;Kristoffer Lium&lt;/a&gt; who is partially sighted. It was an eye-opener for people to see how these two who represented a fairly large group of blind and visually impaired, orients themselves on finn.no.&lt;/p&gt;

&lt;p&gt;A smaller group of developers and interaction designers attended the whole workshop. We got an introduction to the law of accessibility and the requirements in practice. Stein Erik and Kristoffer both emphesized that the most important is to make sites user friendly; not the law itself. If we think of people with disabilities when we develop finn.no, it will lead to a better site for everyone. Even for those who have a headache one day or are sitting on a bumpy bus while browsing finn.no.&lt;/p&gt;

&lt;p&gt;The highlight of the day was perhaps “blindfolded lunch”. All workshop participants put on ski goggles that were spray painted black so no-one could see anything. Then they were ushered around before being led to a prepared lunch table. The canteen staff from FINN helped to create an exciting lunch with different samples in small cups. It was a challenge to know where the food was, to spread the butter on the bread, and simply to understand what we were eating.&lt;/p&gt;

&lt;p&gt;At the end of the day the workshop participants tried out various tools Stein Erik, Kristoffer, and other blind and visually impaired use. The screen was zoomed several hundred percent and the colors were inverted. This is what Kristoffer usually does. With this enlargement and conversion we should try to locate an item on FINN Torget, and try to post a new ad. Again, this was an eye-opener, and we wrote down the areas were we got challenges. This had to be improved! Along with Stein Erik we used a screen reader. And on the last station, we used only the keyboard; no mouse. In all the stations we found areas that could be improved.&lt;/p&gt;

&lt;p&gt;The last half hour of the workshop was used to solve the challenges we had found during the day. The result was several pull requests and a good number of issues in the backlog to make finn.no a better site for everyone!&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/2014-11-19-workshop-with-blindfolded-lunch/keynote.JPG&quot; alt=&quot;keynote&quot; /&gt;
&lt;img src=&quot;/images/2014-11-19-workshop-with-blindfolded-lunch/in-transport.jpeg&quot; alt=&quot;in-transport&quot; /&gt;
&lt;img src=&quot;/images/2014-11-19-workshop-with-blindfolded-lunch/lunch-blindfolded.JPG&quot; alt=&quot;lunch-blindfolded&quot; /&gt;
&lt;img src=&quot;/images/2014-11-19-workshop-with-blindfolded-lunch/lunch-blindfolded2.jpg&quot; alt=&quot;lunch-blindfolded2&quot; /&gt;
&lt;img src=&quot;/images/2014-11-19-workshop-with-blindfolded-lunch/station1.JPG&quot; alt=&quot;station1&quot; /&gt;
&lt;img src=&quot;/images/2014-11-19-workshop-with-blindfolded-lunch/station2.JPG&quot; alt=&quot;station2&quot; /&gt;&lt;/p&gt;
</content>
        
        <author>
            <name>Lotte Johansen</name>
            
            <email></email>
            
        </author>
        
    </entry>
    
    <entry>
        <title>Log4j2 - dependencies</title>
        <link rel="alternate" type="text/html" href="https://tech.finn.no2014/10/27/log4j2-bringing-together-the-dependencies/"/>
        <updated>2014-10-27T11:50:00+00:00</updated>
        <id>https://tech.finn.no/2014/10/27/log4j2-bringing-together-the-dependencies</id>
        <content type="html">&lt;p&gt;To make it easy for all our codebases, to automatically have all logging abstractions pointing towards log4j2 along with logstash configured, we have bundled together the dependencies via one library.
These dependencies look like:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-java&quot; data-lang=&quot;java&quot;&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;!--&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;core&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;libraries&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;--&amp;gt;&lt;/span&gt;  
&lt;span class=&quot;n&quot;&gt;org&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;apache&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;logging&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;log4j&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;log4j&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;api:&lt;/span&gt;&lt;span class=&quot;mf&quot;&gt;2.0&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;  
&lt;span class=&quot;n&quot;&gt;org&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;apache&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;logging&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;log4j&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;log4j&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;core:&lt;/span&gt;&lt;span class=&quot;mf&quot;&gt;2.0&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;  
&lt;span class=&quot;n&quot;&gt;com&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;lmax&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;disruptor:&lt;/span&gt;&lt;span class=&quot;mf&quot;&gt;3.2&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;  

&lt;span class=&quot;o&quot;&gt;&amp;lt;!--&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;commons&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;logging&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;--&amp;gt;&lt;/span&gt;  
&lt;span class=&quot;n&quot;&gt;org&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;apache&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;logging&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;log4j&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;log4j&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;jcl:&lt;/span&gt;&lt;span class=&quot;mf&quot;&gt;2.0&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;  
&lt;span class=&quot;n&quot;&gt;org&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;apache&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;logging&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;log4j&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;log4j&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;mf&quot;&gt;1.2&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;api:&lt;/span&gt;&lt;span class=&quot;mf&quot;&gt;2.0&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;  

&lt;span class=&quot;o&quot;&gt;&amp;lt;!--&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;slf4j&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;--&amp;gt;&lt;/span&gt;  
&lt;span class=&quot;n&quot;&gt;org&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;slf4j&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;slf4j&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;api:&lt;/span&gt;&lt;span class=&quot;mf&quot;&gt;1.7&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;7&lt;/span&gt;  

&lt;span class=&quot;o&quot;&gt;&amp;lt;!--&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;JUL&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;routed&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;through&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;slf4j&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;--&amp;gt;&lt;/span&gt;  
&lt;span class=&quot;n&quot;&gt;org&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;apache&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;logging&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;log4j&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;log4j&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;slf4j&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;impl:&lt;/span&gt;&lt;span class=&quot;mf&quot;&gt;2.0&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;  

&lt;span class=&quot;o&quot;&gt;&amp;lt;!--&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;override&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;old&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;log4j&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;with&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;an&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;empty&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;jarfile&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;--&amp;gt;&lt;/span&gt;  
&lt;span class=&quot;o&quot;&gt;&amp;lt;!--&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;incase&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;it&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;gets&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;re&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;introduced&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;transitively&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;--&amp;gt;&lt;/span&gt;  
&lt;span class=&quot;nl&quot;&gt;log4j:log4j:&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;empty&lt;/span&gt;  

&lt;span class=&quot;o&quot;&gt;&amp;lt;!--&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;logstash&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;--&amp;gt;&lt;/span&gt;  
&lt;span class=&quot;n&quot;&gt;net&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;logstash&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;log4j2&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;log4j2&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;logstash&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;jsonevent&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;layout:&lt;/span&gt;&lt;span class=&quot;mf&quot;&gt;3.0&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;finn&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;  &lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

</content>
        
        <author>
            <name>mick</name>
            
            <email></email>
            
        </author>
        
    </entry>
    
    <entry>
        <title>Log4j2 in production – making it fly</title>
        <link rel="alternate" type="text/html" href="https://tech.finn.no2014/07/01/log4j2-in-production-making-it-fly/"/>
        <updated>2014-07-01T22:17:00+00:00</updated>
        <id>https://tech.finn.no/2014/07/01/log4j2-in-production-making-it-fly</id>
        <content type="html">&lt;p&gt;&lt;br /&gt;
Now that &lt;a href=&quot;http://logging.apache.org/log4j/2.x/&quot;&gt;log4j2&lt;/a&gt; is the predominant logging framework in use at FINN.no why not share the good news with the world and try to provide a summary over the introduction of this exciting new technology into our platform.&lt;/p&gt;

&lt;h3&gt;let's just one logging framework&lt;/h3&gt;
&lt;p&gt;In beginning of September 2013 it became the responsibility of one of our engineering teams to introduce a “best practice for logging” for all of FINN.&lt;/p&gt;

&lt;p&gt;The proposal put forth was that we can standardise on one backend logging framework while there would be no need to standardise on the abstraction layer directly used in our code.&lt;/p&gt;

&lt;p&gt;The rationale to &lt;strong&gt;not standardising the logging abstractions&lt;/strong&gt; was…&lt;/p&gt;

&lt;p&gt; • nearly every codebase, through a tree of dependencies, already includes all the different logging abstraction libraries, so the hard work of configuring them against the chosen logging framework is still required,
 • the different APIs to the different logging abstractions are not difficult for programmers to go between from one project to another.&lt;/p&gt;

&lt;p&gt;While the rationale to &lt;strong&gt;standardising the logging framework&lt;/strong&gt; was…&lt;br /&gt;
 • makes life easier for programmers with one documented “best practice” for all,&lt;br /&gt;
 • makes it possible through an in-house library to configure all abstraction layers, creating less configuration for programmers,
 • makes life easier for operations knowing all jvms log the same way,&lt;br /&gt;
 • makes life easier for operations knowing all log files follow the same format.&lt;/p&gt;

&lt;h3&gt;Log4j2 wins HANDS down&lt;/h3&gt;

&lt;p&gt;Log4j2 was chosen as the logging framework given…
 • it provided all the features that logback was becoming popular for,
 • between old log4j and logback it was the only framework that was written in java with modern concurrency (ie not hard synchronised methods/blocks),
 • it provided a significant performance improvement (1000-10000 times faster)&lt;br /&gt;
 • it consisted of a more active community (logback has been announced as the replacement for the old log4j, but log4j2 saw a new momentum in its apache community).&lt;/p&gt;

&lt;p&gt;This proposal was checked with a vote by finn programmers
 • 73% agreed, 27% were unsure, no-one disagreed.&lt;/p&gt;

&lt;h3&gt;when nightly compression jams&lt;/h3&gt;
&lt;p&gt;Earlier on in this process we hit a bug with the old log4j where nightly compression of already rotated logfiles were locking up all requests in any (or most) jvms for up to ten seconds. This fault came back to poor java concurrency code in the original log4j (which logback cloned). Exacerbated by us having scores of jvms for all our different microservices running on the same machines so that when nightly compression kicked in it did so all in parallel.  Possible fixes here were to&lt;br /&gt;
a) stop compression of log files,&lt;br /&gt;
b) make loggers async, or&lt;br /&gt;
c) migrate over quickly to log4j2.&lt;/p&gt;

&lt;p&gt;After some investigation, (c) was ruled out because there was no logstash plugin for log4j2 ready and moving forward without the json logfiles and the logstash &amp;amp; kibana integration was not an option. (a) was chosen as a temporary solution.&lt;/p&gt;

&lt;h3&gt;ready, steady, go…&lt;/h3&gt;
&lt;p&gt;Later on, when we started the work on upgrading all our services from thrift-0.6.1 up to thrift-0.9.1, we took the opportunity to kill two birds with the one stone. Log4j2 was out of beta, and we had ironed out the issues around the logstash plugin.&lt;/p&gt;

&lt;p&gt;We’d be lying if we told you it was all completely pain free,
introducing Log4j2 came with some concerns and hurdles.&lt;/p&gt;

&lt;p&gt; • Using a release candidate of log4j2 in production lead to some concerns. So far the only consequence was slow startup times (eg even small services paused for ~8 seconds during startup). This was due to log4j2 having to scan all classes for possible log4j plugins. This problem was fixed in 2.0-rc2. On the bright side – our use of the release candidate meant we spotted early and getting the upcoming initial release of log4j2 to support shaded jarfiles, of which we are heavily dependent on.
 • Operation’s had expressed concerns over nightly compression, raised from the earlier problem around nightly compression, that even if code no longer blocked while the compressing was happening in a background thread the amount of parallel compressions spawned would lead to IO contention which in turn leads to CPU contention. Because of this very real concern extensive tests have been executed, so far they’ve shown no measurable (under 1ms) impact exists upon services within the FINN platform. Furthermore this problem can be easily circumvented by adding the &lt;a href=&quot;http://logging.apache.org/log4j/2.x/manual/appenders.html#TriggeringPolicies&quot;&gt;SizeBasedTriggeringPolicy&lt;/a&gt; to your appender, thereby enforcing a limit on how much parallel compression can happen at midnight.
 • The new &lt;a href=&quot;https://github.com/finn-no/log4j2-logstash-jsonevent-layout&quot;&gt;logstash plugin&lt;/a&gt; (which finn has actively contributed to on github) caused a few breakages to the format expected by our custom logstash parsers written by operations. Unfortunately this parser is based off the old log4j format, of which we are trying to escape. Breakages here were: log events on separate lines, avoiding commas are the end of lines between log events, thread context in wrong format, etc. These were tackled with pull requests on github and patch versions of our commons-service (the library used to pre-configure the correct dependency tree for log4j2 artifacts and properly plugging in all the different logging abstraction libraries).
 • Increased memory from switching to sync loggers to async loggers impacted services with very small heap. The &lt;a href=&quot;http://logging.apache.org/log4j/2.x/manual/async.html#AllAsync&quot;&gt;async&lt;/a&gt; logger used is based of the lmax-disruptor which pre-allocates its ringBuffer with its maximum capacity. By default this ringBuffer is configured to queue at maximum 256k log events. This can be adjusted with the “&lt;a href=&quot;http://logging.apache.org/log4j/2.x/manual/async.html#MixedSync-Async&quot;&gt;AsyncLoggerConfig.RingBufferSize&lt;/a&gt;” system property.&lt;/p&gt;

&lt;h3&gt;simply beautiful&lt;/h3&gt;
&lt;p&gt;To wrap it up the hurdles have been there, but trivial and easy to deal with, while the benefits of introducing log4j2, and moving to async loggers, make it well worth it…
 • The “best practice” for log4j2 included changing all loggers to be async, and this means that the performance of the FINN platform (which consists primarily of in-memory services) is no longer tied to and effected by how disks are performing (it was crazy that it was before).
 • More and more applications are generating consistent logfiles according to our best practices.
 • More and more applications are actually plugging in the various different logging abstractions used by all their various third-party dependencies.
 • All the advantages people liked about logback.
 • An easier approach to changing loglevels at runtime through jmx.
 • Profiling applications in crisis easier for outsiders (one less low-level behavioural difference).
 • Loggers are no longer a visible bottleneck in jvms under duress.
 • And naturally &lt;a href=&quot;http://www.javacodegeeks.com/2013/07/log4j-2-performance-close-to-insane.html&quot;&gt;the performance increase&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Because of the significant performance provided by the lmax-disruptor we also use the open-sourced &lt;a href=&quot;https://github.com/finn-no/statsd-lmax-disruptor-client&quot;&gt;statsd client&lt;/a&gt; that takes advantage of it.&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;http://www.deviantart.com/art/Fire-and-Ice-129732715&quot;&gt;&lt;img src=&quot;http://fc06.deviantart.net/fs46/f/2009/197/a/0/Fire_and_Ice_by_Canadian_fast_food.jpg&quot; alt=&quot;Fire and Ice by Canadian fast food&quot; /&gt;&lt;/a&gt;&lt;/p&gt;
</content>
        
        <author>
            <name>mick</name>
            
            <email></email>
            
        </author>
        
    </entry>
    
    <entry>
        <title>We love npm</title>
        <link rel="alternate" type="text/html" href="https://tech.finn.no2013/12/03/we-love-npm/"/>
        <updated>2013-12-03T10:38:00+00:00</updated>
        <id>https://tech.finn.no/2013/12/03/we-love-npm</id>
        <content type="html">&lt;p&gt;&lt;img src=&quot;https://scalenpm.org/img/npm.png&quot; alt=&quot;npm Logo&quot; /&gt;
What? Why is FINN.no donating to scale NPM? I thought you guys were a pure Java shop? It is true, we used to be a pure Java-shop. However over the past three years we have adopted new technologies to solve specific problems. We have used &lt;a href=&quot;https://www.ruby-lang.org/en/&quot;&gt;Ruby&lt;/a&gt; and &lt;a href=&quot;http://cukes.info/&quot;&gt;Cucumber&lt;/a&gt; for some time to make a platform for continuos delivery and it has worked out beautifully! Our front-end developers have been forced to deal with out dated and not suitable tools for doing their job. This is largely due to the fact that all innovation when it comes to front-end development does not happen in the Java community. Most of the exciting tools are written in Node and this has become a frustration and a challenge for us.&lt;/p&gt;

&lt;p&gt;In the past year FINN have been gradually making a transition away from using only Java-based tools for front-end development and towards a NodeJS powered tool set. We are now at a point were we are on the brink of rolling this out for our projects. Having worked with Node for a while we have learned to appreciate the Node ecosystem which is NPM. Being part of such a vibrant ecosystem of modules makes the transition easier and it also inspires us to become better at giving back. Therefore we are trying to give back to NPM, when we can.&lt;/p&gt;

&lt;p&gt;When the &lt;a href=&quot;http://scalenpm.org&quot;&gt;scale NPM&lt;/a&gt; campaign was launched it was obvious that this was something we wanted to be apart of. It is an investment in our own happiness in a sense, as NPM is becoming a very important part for our technology portfolio.&lt;/p&gt;
&lt;h2&gt;Nodeify all the things&lt;/h2&gt;
&lt;p&gt;So were is it that we use Node in our technology stack today? Earlier this year we moved away from &lt;a href=&quot;https://code.google.com/p/js-test-driver/wiki/&quot;&gt;JsTestDriver&lt;/a&gt; in favor of &lt;a href=&quot;http://karma-runner.github.io&quot;&gt;Karma-runner&lt;/a&gt;. This meant that we needed to create a trojan horse containing the goodness of Node/NPM into existing Java projects without causing too many problems for developers with no knowledge of Node. A part of this scheme was the &lt;a href=&quot;https://github.com/eirslett/frontend-maven-plugin&quot;&gt;frontend-maven-plugin&lt;/a&gt;, which enables us to have control of which Node projects use and allows developers without Node previously installed to build projects and run tests without having to learn anything about Node.&lt;/p&gt;

&lt;p&gt;Currently we are working towards removing the need for using Maven to build and deploy pure JavaScript projects using Node. The end result is of course to have more web applications built using Node. Today we have just two which are running in a production environment.&lt;/p&gt;
</content>
        
        <author>
            <name>espen</name>
            
            <email></email>
            
        </author>
        
    </entry>
    
    <entry>
        <title>Low-fi Coffe Surveilence</title>
        <link rel="alternate" type="text/html" href="https://tech.finn.no2013/11/29/low-fi-coffe-surveilence/"/>
        <updated>2013-11-29T09:03:00+00:00</updated>
        <id>https://tech.finn.no/2013/11/29/low-fi-coffe-surveilence</id>
        <content type="html">&lt;p&gt;There is a Norwegian start-up called &lt;a href=&quot;http://appear.in&quot;&gt;AppearIn&lt;/a&gt; which enables anyone to do online video conferencing with only a web browser. They utilize &lt;a href=&quot;http://webrtc.org&quot;&gt;WebRTC&lt;/a&gt; to empower users to create rooms where they can hang out, do meetings, etc. We had the pleasure of having Ingrid form Appear In over for a visit and she suggested we’d contribute to their blog about use cases for appear in. That’s how we became &lt;a href=&quot;http://blog.appear.in/post/68370877054/use-case-7-for-appear-in-monitor-your-coffee-maker&quot;&gt;use case #7&lt;/a&gt; on their blog. Naturally this should’ve been an arduino powered thing with lot’s of sophisticated technology, but we decided we’d just get something simple working first and then make it better later on.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/2013-11-29-low-fi-coffe-surveilence/coffe.jpg&quot; alt=&quot;Low-fi Coffe Surveilence&quot; /&gt;&lt;/p&gt;

&lt;p&gt;This is a really exciting platform as it empowers developers to have video/audio communication with little effort. Dealing with WebRTC, which is still a moving specification, can be quite challenging to implement properly. There’s numerous corner cases and differences between implementations.&lt;/p&gt;

&lt;p&gt;Update: Through the wonders of the internet, it turns out that we have brought the webcam thing full circle. A user on Hacker News &lt;a href=&quot;https://news.ycombinator.com/item?id=6816125&quot;&gt;posted a comment&lt;/a&gt; which linked to a &lt;a href=&quot;https://en.wikipedia.org/wiki/Trojan_Room_coffee_pot&quot;&gt;wikiepedia article about the first webcam&lt;/a&gt;. It turns out the use case for it was exactly the same as we had.&lt;/p&gt;
</content>
        
        <author>
            <name>espen</name>
            
            <email></email>
            
        </author>
        
    </entry>
    
    <entry>
        <title>Package Management conflicts Continuous Delivery</title>
        <link rel="alternate" type="text/html" href="https://tech.finn.no2013/06/26/package-management-conflicts-continuous-delivery/"/>
        <updated>2013-06-26T20:42:28+00:00</updated>
        <id>https://tech.finn.no/2013/06/26/package-management-conflicts-continuous-delivery</id>
        <content type="html">&lt;p&gt;&lt;img src=&quot;http://www.slashroot.in/sites/default/files/styles/article_image_full_node/public/field/image/yum%20package_0.png&quot; alt=&quot;Package&quot; /&gt;&lt;/p&gt;

&lt;p&gt;The idea of package management is to correctly operate and bundle together various components in any system. The practice of package management is a consequence from the design and evolution of each component’s API.&lt;/p&gt;

&lt;h3 id=&quot;package-management-is-tedious&quot;&gt;Package management is tedious&lt;/h3&gt;

&lt;p&gt;but necessary. It can also help to address the ‘fear of change’.&lt;/p&gt;

&lt;p&gt;We can minimise package management by minimising API. But we can’t minimise API if we don’t have experience with where it comes from. You can’t define for yourself what the API of your code is. It is well beyond that of your public method signatures. Anything that with change can break a consumer is API.&lt;/p&gt;

&lt;h3 id=&quot;continuous-delivery-isnt-void-of-api&quot;&gt;Continuous Delivery isn’t void of API&lt;/h3&gt;

&lt;p&gt;despite fixed and minimised interfaces between runtime services, each runtime service also contains an API in how it behaves. The big difference though is you own the release change, a la the deployment event, and if things don’t go well you can roll back. Releasing artifacts in the context of package management can not be undone. Once you have released the artifact you must presume someone has already downloaded it and you can’t get it back. The best you can do it release a new version and hope everyone upgrades to it quickly.&lt;/p&gt;

&lt;h3 id=&quot;push-code-out-from-behind-the-shackles-of-package-management&quot;&gt;Push code out from behind the shackles of package management&lt;/h3&gt;

&lt;p&gt;take advantage of continuous delivery! Bearing in mind a healthy modular systems design comes from making sure you got the api design right – so the amount one can utilise CD is ultimately limited, unless you want to throw out modularity. In general we let components low in the stack “be safe” by focusing on api design over delivery time, and the opposite for components high in the stack.&lt;/p&gt;

&lt;h3 id=&quot;high-in-the-stack-doesnt-refer-to-front-end-code&quot;&gt;High in the stack doesn’t refer to front-end code&lt;/h3&gt;

&lt;p&gt;Code at the top of the stack is that free of package management and completely free for continuous deployment. Components with direct consumers no longer sit at the top of the stack. As components consumers multiple, and they become transitive dependencies, they move further down the stack. Typically entropy of the component corresponds to position in the stack. Other components forced into package management can be those where parallel versions need be deployed.&lt;/p&gt;

&lt;h3 id=&quot;some-simple-rules-to-abide-by&quot;&gt;Some simple rules to abide by…&lt;/h3&gt;

&lt;ul&gt;
  &lt;li&gt;
    &lt;p&gt;don’t put configuration into libraries.
  &lt;em&gt;because this creates version-churn and leads to more package management&lt;/em&gt;&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;don’t put services into libraries.
  &lt;em&gt;same reason as above.&lt;/em&gt;&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;don’t confuse deploying with version releases.
  &lt;em&gt;don’t release every artifact as part of a deployment pipeline.
  separate concerns of continuous delivery and package management.&lt;/em&gt;&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;try to use a runtime service instead of a compile-time library.
  &lt;em&gt;this minimises API, in turn minimising package management,&lt;/em&gt;&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;try to re-use standard APIs (REST, message-buses, etc).
  &lt;em&gt;the less API you own the less package management.
  but don’t cheat! data formats are APIs, and anything exposed that breaks stuff when changed is API.&lt;/em&gt;&lt;/p&gt;
  &lt;/li&gt;
&lt;/ul&gt;

</content>
        
        <author>
            <name>mick</name>
            
            <email></email>
            
        </author>
        
    </entry>
    
    <entry>
        <title>Dark Launching and Feature Toggles</title>
        <link rel="alternate" type="text/html" href="https://tech.finn.no2013/06/20/dark-launching-and-feature-toggles/"/>
        <updated>2013-06-20T11:56:20+00:00</updated>
        <id>https://tech.finn.no/2013/06/20/dark-launching-and-feature-toggles</id>
        <content type="html">&lt;p&gt;&lt;a href=&quot;http://be-toru.deviantart.com/art/Dark-Side-of-The-Moon-129409258&quot;&gt;&lt;img src=&quot;http://th02.deviantart.net/fs47/200H/f/2009/194/3/1/Dark_Side_of_The_Moon_by_Be_Toru.png&quot; alt=&quot;Dark side of the moon&quot; /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Make sure to distinguish between these two.&lt;/p&gt;

&lt;p&gt;They are not the same thing,
and it’s a lot quicker to just &lt;em&gt;Dark Launch&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;In addition Dark Launching promotes incremental development, continuous delivery, and modular design. Feature Toggles need not, and can possibly be counter-productive.&lt;/p&gt;

&lt;p&gt;Dark Launching is an operation to silently and freely deploy something. Giving you time to ensure everything operates as expected in production and the freedom to switch back and forth while bugfixing. A Dark Launch’s goal is often about being completely invisible to the end-user. It also isolates the context of the deployment to the component itself.&lt;/p&gt;

&lt;p&gt;Feature Toggling, in contrast, is often the ability to A/B test new products in production. Feature Toggling is typically accompanied with measurements and analytics, eg NetInsight/Google-Analytics. Feature Toggles may also extend to situations when the activation switch of a dark launch can only happen in a consumer codebase, or when only some percentage of executions will use the dark launched code.&lt;/p&gt;

&lt;p&gt;Given that one constraint of any decent enterprise platform is that all components must fail gracefully Dark Launching is the easiest solution, and a golden opportunity to ensure your new code fails gracefully. Turn the new module on, and it’s dark launched and in use, any problems turn it off again. You also shouldn’t have to worry about only running some percentage of executions against the new code, let it all go to the new component, if the load is too much the excessive load should also fail-gracefully and fall back to the old system.&lt;/p&gt;

&lt;p&gt;Dark Launching is the simple approach as it requires no feature toggling framework, or custom key-value store of options. It is a DevOps goal that remains best isolated to the context of DevOps – in a sense the ‘toggling’ happens through operations and not through code. When everything is finished it is also the easier approach to clean up. Dealing with and cleaning up old code takes up a lot of our time and is a significant hindrance to our ability to continually innovate. In contrast any feature toggling framework can risk encouraging a mess of outdated, arcane, and unused properties and code paths. KISS: always try and bypass a feature toggle framework by owning the ‘toggle’ in your own component, rather than forcing it out into consumer code.&lt;/p&gt;

&lt;p&gt;Where components have &lt;a href=&quot;http://en.wikipedia.org/wiki/Command-query_separation&quot;&gt;CQS&lt;/a&gt; it gets even better. First the command component is dark launched, whereby it runs in parallel, and data can be test between the old and new system (blue-green deployments). Later on the query component is dark launched. While the command components can run and be used in parallel for each and every request the query components cannot. When the dark launch of the query component is final the old system is completely turned off.&lt;/p&gt;

&lt;p&gt;Now the intention of this post isn’t to say we don’t need feature toggling, but to give terminology to, and distinguish, between two different practices instead of lumping everything under the term “feature toggling”. And to discourage using a feature toggling framework for everything because we fail to understand the simpler alternative.&lt;/p&gt;

&lt;p&gt;In the context of front-end changes, it’s typical that for a new front-end feature to come into play there’s been some new backend components required. These will typically have been dark launched. Once that is done, the front-end will introduce a feature toggle rather than dark launch because it’s either introducing something new to the user or wanting to introduce something new to a limited set of users. So even here dark launching can be seen not as a “cool” alternative, but as the prerequisite practice.&lt;/p&gt;

&lt;p&gt;Reference: “&lt;a href=&quot;http://bit.ly/16IgCit&quot;&gt;DevOps for Developers&lt;/a&gt;” By Michael Hüttermann&lt;/p&gt;
</content>
        
        <author>
            <name>mick</name>
            
            <email></email>
            
        </author>
        
    </entry>
    
    <entry>
        <title>Conquering the kingdom of Java with a NodeJS trojan horse</title>
        <link rel="alternate" type="text/html" href="https://tech.finn.no2013/06/06/conquering-the-kingdom-of-java-with-a-nodejs-trojan-horse/"/>
        <updated>2013-06-06T07:36:00+00:00</updated>
        <id>https://tech.finn.no/2013/06/06/conquering-the-kingdom-of-java-with-a-nodejs-trojan-horse</id>
        <content type="html">&lt;p&gt;&lt;img src=&quot;http://upload.wikimedia.org/wikipedia/commons/6/6e/Trojan_Horse_by_A_Yakovlev_1911.jpg&quot; alt=&quot;trojan horse&quot; /&gt;
Most of FINN.no is built with Java and we’ve been a Java shop for 6-7 years. With the professionalization of front-end development we saw that the tools available in the Java Web Development ecosystem was preventing us from delivering rapidly and with high quality.
The trend was that the tools we used weren’t keeping up with the pace set by people creating tools in &lt;a href=&quot;http://nodejs.org/&quot;&gt;Node&lt;/a&gt;’s &lt;acronym title=&quot;Node Package Management&quot;&gt;NPM&lt;/acronym&gt; module ecosystem. When working on pet projects we could use these tools, but in our daily job we were working with second rate tools. This was a frustration for many of us, so we set out to work out how we could utilize Node without having to rewrite all our applications.&lt;/p&gt;

&lt;h2&gt;In the belly of the beast that is Maven&lt;/h2&gt;
&lt;p&gt;We use &lt;a href=&quot;http://maven.apache.org/&quot;&gt;Maven&lt;/a&gt; to build and test all our applications. This includes continuous deployment and quality analysis. Our reporting tools requires projects to be built with Maven in order to work. To introduce Node-based tools for front-end tasks, we had to be able to run them from Maven.&lt;/p&gt;

&lt;p&gt;The easiest way to get started is just to use the Maven exec plugin. It allows you to run commands like on the command line. This approach worked, but there was one catch: it relied upon &lt;a href=&quot;http://nodejs.org/&quot;&gt;Node&lt;/a&gt; + &lt;a href=&quot;https://www.npmjs.org/&quot;&gt;NPM&lt;/a&gt; to be installed on every developer machine as well as in our &lt;acronym title=&quot;Continuous Integration&quot;&gt;CI&lt;/acronym&gt; environment. Forcing 100+ developers to install Node and keep up with versions was something which wasn’t really acceptable for the rest of the organization. Enter The Maven Front-end Plugin&lt;/p&gt;

&lt;h2&gt;Behold! The Frontend Maven plugin&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/eirslett&quot;&gt;Erik Sletteberg&lt;/a&gt;, who started out working as a summer intern and is now working at FINN.no full time, helped solve the problem of Node having to be installed. He created the &lt;a href=&quot;https://github.com/eirslett/frontend-maven-plugin&quot;&gt;Frontend Maven Plugin&lt;/a&gt; which install Node+NPM in the project folder. The plugin also has tasks for running NPM, &lt;a href=&quot;http://gruntjs.com/&quot;&gt;Grunt&lt;/a&gt;, &lt;a href=&quot;http://gulpjs.com/&quot;&gt;Gulp&lt;/a&gt; and similar.
Because of the tool we were able to use Node within a Maven web application module, which was awesome! It meant we could use build-, test and code analysis tools developed in Node and have them report into our existing tool chain.&lt;/p&gt;

&lt;h2&gt;Independence with Standalone Node modules&lt;/h2&gt;
&lt;p&gt;In the Java stack there is no real support for distributing front end dependencies for things like JavaScript, CSS, etc. There are plugins for things like Require-js for JavaScript, but they were all suffering from the same problem: the plugins couldn’t keep up with the pace set by the Node ecosystem when it came to iterating on these kinds of tools.
Our setup at the time was to split out any front-end resource which needed to be distributed into Maven Web Application modules and include them like any other Maven dependency in the POM. The release process for Maven artifacts is tedious. When importing dependencies into an existing Maven module you had to perform all kinds of URL rewriting to make paths correct. In short, this was not a very smooth way of sharing client side resources.&lt;/p&gt;

&lt;p&gt;Having Node and NPM running inside Maven, it was only natural for us to look at how we could create &lt;a href=&quot;http://en.wikipedia.org/wiki/CommonJS&quot;&gt;Common JS&lt;/a&gt; modules of our existing JS modules which were now in Maven. The short term goal was to get rid of Maven while developing these modules. The long term goal is to have client side dependencies declared as regular Node dependencies in projects.&lt;/p&gt;

&lt;h2&gt;Maven-deploy&lt;/h2&gt;
&lt;p&gt;In order to make the transition as smooth as possible we needed to have Node modules which we were able to deploy to Maven repositories in addition to our private NPM registry. &lt;a href=&quot;https://github.com/gregersrygg&quot;&gt;Gregers G. Rygg&lt;/a&gt; from the Front-end Core Team set out to create the &lt;a href=&quot;https://github.com/finn-no/maven-deploy&quot;&gt;maven-deploy&lt;/a&gt; module which solves this (if you use Gulp, you can use the &lt;a href=&quot;https://www.npmjs.org/package/gulp-maven-deploy&quot;&gt;gulp-maven-deploy&lt;/a&gt; plugin). It can either install the artifact locally under M2_HOME or deploy it to a remote repositories. This way we could have Node modules behave as if they were Maven modules, which in turn meant that existing Java Web Application didn’t have to be changed in order for us to use Node.&lt;/p&gt;

&lt;h2&gt;In closing&lt;/h2&gt;
&lt;p&gt;This was by no means the fastest way to introduce Node into an existing architecture. If you’re a small shop with few people, you should adopt a more aggressive approach. Our approach however works beautifully for those of you working in larger organizations were you need to do things in a more subtle way. Taking small steps and always making sure everyone can perform their job as normal was one of our keys to succeeding. If we’d gone all out and created additional work for many developers, our Node adventure would’ve never happened.&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;//github.com/leftieFriele&quot;&gt;Espen Dalløkken&lt;/a&gt; from the Front-end Core Team &lt;a href=&quot;http://2014.boosterconf.no/talks/232&quot;&gt;did a short lightening talk&lt;/a&gt; about this very subject at &lt;a href=&quot;http://www.boosterconf.no/&quot;&gt;Booster Conference&lt;/a&gt; 2014 in Bergen:&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;http://vimeo.com/89938193&quot;&gt;Conquering the wicked kingdom of Java with a NodeJS Trojan horse - Espen Dalløkken&lt;/a&gt; from &lt;a href=&quot;http://vimeo.com/boosterconf&quot;&gt;Booster conference&lt;/a&gt; on &lt;a href=&quot;https://vimeo.com&quot;&gt;Vimeo&lt;/a&gt;.&lt;/p&gt;
</content>
        
        <author>
            <name>espen</name>
            
            <email></email>
            
        </author>
        
    </entry>
    
    <entry>
        <title>given the git</title>
        <link rel="alternate" type="text/html" href="https://tech.finn.no2013/03/20/given-the-git/"/>
        <updated>2013-03-20T11:14:08+00:00</updated>
        <id>https://tech.finn.no/2013/03/20/given-the-git</id>
        <content type="html">&lt;p&gt;&lt;img src=&quot;http://t2.gstatic.com/images?q=tbn:ANd9GcT06Df3cXPHx1qzMy6AV2ibmUOXlKFstSObwkgmQvdUpIC16Uz_&quot; alt=&quot;Frog&quot; /&gt;&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;“There is no way to do CVS right.” - Linus&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;FINN is migrating from subversion to the ever trendy git.&lt;/strong&gt;
We’ve waited years for it to happen,
here we’ll try to highlight why and how we are doing it.&lt;/p&gt;

&lt;h3 id=&quot;working-together&quot;&gt;Working together&lt;/h3&gt;

&lt;p&gt;There’s no doubt that git gives us a cleaner way of working on top of each other. Wherever you promote peer review you need a way of working with changesets from one computer to the next without having to commit to and via the trunk where everyone is affected. Creating custom branches comes with too much (real or perceived) overhead, so the approach at best falls to throwing patches around. Coming away from a pair-programming session it’s better when developers go back to their own desk with such a patch so they can work on it a bit more and finish it properly with tests, docs, and a healthy dose of clean coding. It properly entitles them as the author rather than appearing as if someone else took over and committed their work. Git’s decentralisation of repositories provides the cleaner way by replacing these patches with private repositories and its easy to use branches.&lt;/p&gt;

&lt;h3 id=&quot;individual-productivity&quot;&gt;Individual productivity&lt;/h3&gt;

&lt;p&gt;Git improves the individual’s productivity with benefits of &lt;a href=&quot;http://git-scm.com/book/en/Git-Tools-Stashing&quot;&gt;stashing&lt;/a&gt;, &lt;a href=&quot;http://git-scm.com/book/en/Git-Tools-Rewriting-History&quot;&gt;squashing&lt;/a&gt;, &lt;a href=&quot;http://git-scm.com/book/en/Git-Basics-Undoing-Things&quot;&gt;reseting&lt;/a&gt;, and &lt;a href=&quot;http://git-scm.com/book/en/Git-Branching-Rebasing&quot;&gt;rebasing&lt;/a&gt;. A number of programmers for a number of years were already on the bandwagon using git-svn against our subversion repositories. This was real proof of the benefits, given the headaches of git-svn (can’t &lt;a href=&quot;http://stackoverflow.com/questions/5652521/does-git-svn-handle-moved-files&quot;&gt;move&lt;/a&gt; files and &lt;a href=&quot;http://stackoverflow.com/questions/3011625/git-mv-and-only-change-case-of-directory&quot;&gt;renaming&lt;/a&gt; files gives corrupted repositories)&lt;/p&gt;

&lt;p&gt;With Git, work is encouraged to be done on feature branches and merged in to master as complete (squashed/rebased) changesets with clean and summarised commit messages.&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;
    &lt;p&gt;This improves efforts towards continuous deployment due to a more stable HEAD.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;Rolling back any individual feature regardless of its age is a far more manageable task.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;By squashing all those checkpoint commits we typically make we get more meaningful, contextual, and accurate &lt;a href=&quot;http://thomashw.github.com/blog/2012/12/02/commit-messages/&quot;&gt;commit messages&lt;/a&gt;.&lt;/p&gt;
  &lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Reading isolated and complete changesets provides clear oversight, to the point reading code history becomes enjoyable, rather than a chore. Equally important is that such documentation that resides so close to, if not with, the code comes with a real permanence. There is no documentation more accurate over all of time than the code itself and the commit messages to it. Lastly writing and rewriting good commit message will alleviate any culture of jira issues with vague, or completely inadequate, descriptions as teams hurry themselves through scrum methodologies where little attention is given to what is written down.&lt;/p&gt;

&lt;h3 id=&quot;maintaining-forks&quot;&gt;Maintaining forks&lt;/h3&gt;

&lt;p&gt;Git makes maintaining forks of upstream projects easy.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;With Git&lt;/strong&gt;
 fork the upstream repository,
branch,  fix and commit,
create the upstream pull request,
 while you wait for the pull request to be accepted/rejected use your  custom inhouse artifact.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;With Subversion&lt;/strong&gt;
file an upstream issue,
checkout the code,
fix and store in a patch attached to the issue,
while you wait use the inhouse custom artifact from the patched but uncommited codebase.&lt;/p&gt;

&lt;p&gt;Both processes are largely the same but it’s safer and so much easier using a forked git repository over a bunch of patch files.&lt;/p&gt;

&lt;h3 id=&quot;has-git-flow-any-advantage&quot;&gt;Has Git-Flow any advantage?&lt;/h3&gt;

&lt;p&gt;We’re putting some thoughts into how we were to organise our repositories, branches, and workflows. The best introductory article we’ve so far come across is from &lt;a href=&quot;http://sandofsky.com/blog/git-workflow.html&quot;&gt;sandofsky&lt;/a&gt; and should be considered mandatory reading. Beyond this one &lt;a href=&quot;http://nvie.com/posts/a-successful-git-branching-model/&quot;&gt;popular&lt;/a&gt; approach is organising branches using &lt;a href=&quot;http://jeffkreeftmeijer.com/2010/why-arent-you-using-git-flow/&quot;&gt;Git Flow&lt;/a&gt;. This seemed elegant but upon closer inspection comes with more disadvantages than benefits…&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;
    &lt;p&gt;the majority needs to ‘checkout develop’ after cloning (there are more developers than ops),&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;master is but a sequence of “tags” and therefore develop becomes but a superfluous branch, a floating “stable” tag instead is a better solution over any branch that simply marks states,&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;it was popular but didn’t form any standard,&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;requires a script not available in all GUIs/IDEs, otherwise it is but a convention,&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;prevents you from getting your hands dirty with the real Git, how else are you going to learn?,&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;it goes against having focus and gravity towards continuous integration that promotes an always stable HEAD. That is we desire less stabilisation and qa branches, and more individual feature and fix branches.&lt;/p&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href=&quot;http://scottchacon.com/2011/08/31/github-flow.html&quot;&gt;GitHub Flow&lt;/a&gt; gives a healthy critique of Git-Flow and it helped identify our style better. GitHub Flow focus’ on continuous integration, “deploy to production every day” rather than releasing, and relying on git basics rather than adding another plugin to our development environment.&lt;/p&gt;

&lt;h3 id=&quot;our-workflows&quot;&gt;Our workflows&lt;/h3&gt;

&lt;p&gt;So we devised two basic and flexible workflows: one for applications and one for services and libraries. Applications are end-user products and stuff that has no defined API like batch jobs. Services are our runtime services that builds up our platform, each come with a defined API and client-server separation in artifacts. Applications are deployed to environments, but because no other codebase depends on them their artifacts are never released. Services, with their APIs and client-side libraries, are released using &lt;a href=&quot;http://semver.org/&quot;&gt;semantic versions&lt;/a&gt;, and the server-side code to them is deployed to environments in the same manner as Applications. The differences between Applications and Services/Libraries warrant two different workflow styles.&lt;/p&gt;

&lt;p&gt;Both workflow styles use master as the stable branch. Feature branches come off master. An optional “integration” (or “develop”) branch  may exist between master and feature branches, for example CI build tools might automatically merge integration changes back to master, but take care not to fall into the anti-pattern of using merges to mark state.&lt;/p&gt;

&lt;p&gt;The workflow for Applications may use an optional stable branch where deployments are made from, this is used by projects that have not perfected continuous deployment. Here bug fix branches are taken from the stable branch, and forward ported to master. For applications practising continuous deployment a more GitHub approach may be taken where deployments from finished feature branches occur and after successfully running in production such feature branches are then merged to master.&lt;/p&gt;

&lt;p&gt;The workflow for Services is based upon each release creating a new semantic version and the git tagging of it. Continuous deployment off master is encouraged but is limited to how compatible API and the client libraries are against the HEAD code in the master branch – code that is released and deployed must work together. Instead of the optional stable branch, optional versioned branches may exist. These are created lazy from the release tag when the need for a bug fix on any previous release arises, or when master code no longer provides compatibility to the released artifacts currently in use. The latter case highlights the change when deployments start to occur off the versioned branch instead of off master. Bug fix branches are taken from the versioned branch, and forward ported to master.&lt;/p&gt;

&lt;p&gt;Similar to Services are Libraries. These are artifacts that have no server-side code. They are standalone code artifacts serving as compile-time dependencies to the platform. A Library is released, but never itself deployed to any environment. Libraries are void of any efforts towards continuous deployments but otherwise follow very similar workflow as Services – typically they give longer support to older versions and therefore have multiple release branches active.&lt;/p&gt;

&lt;p&gt;How any team operates their workflow is up to them, free to experiment to see what is effective. At the end of the day as long as you understand the differences between &lt;a href=&quot;http://git-scm.com/book/en/Git-Branching-Basic-Branching-and-Merging&quot;&gt;merge&lt;/a&gt; and &lt;a href=&quot;http://git-scm.com/book/en/Git-Branching-Rebasing&quot;&gt;rebase&lt;/a&gt; then evolving from one workflow to another over time shouldn’t be a problem.&lt;/p&gt;

&lt;h3 id=&quot;infrastructure-atlassian-stash&quot;&gt;Infrastructure: Atlassian Stash&lt;/h3&gt;

&lt;p&gt;The introduction of Git was stalled for a year from our Ops team as there was no repository management software they were happy enough with to support (integration with existing services was important, particularly Crowd). Initially they were waiting on either Gitolite or Gitorius. Eventually someone suggested Stash from Atlassian and after a quick trial this was to be it. We’re using a number of Atlassian products already: Jira, Fisheye, Crucible, and Confluence; so the integration factor was good and so we’ve paid for a product that was at the time incredibly overpriced with next to nothing on its feature list. One feature the otherwise very naked Stash comes with is &lt;em&gt;Projects&lt;/em&gt;, which provides a basic grouping of repositories. We’ve used this grouping to organise our repositories based on our  architectural layers: “applications”, “services”, “libraries”, and “operations”. The idea is not to build fortresses with projects based on teams but to best please the outsider who is looking for some yet unknown codebase and only knows what &lt;em&gt;type&lt;/em&gt; of codebase it is. We’re hoping that Atlassian adds &lt;a href=&quot;https://jira.atlassian.com/browse/STASH-3217&quot;&gt;labels&lt;/a&gt; and &lt;a href=&quot;https://jira.atlassian.com/browse/STASH-3216&quot;&gt;descriptions&lt;/a&gt; to repositories to further help organisation. Permissions is easy, full read and write access to everyone, we’ll learn quickest when we’re all free to make mistakes, it’s all under version control at the end of the day.&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;http://www.atlassian.com/software/stash/&quot;&gt;&lt;img src=&quot;http://www.atlassian.com/software/stash/overview/feature-overview/featureItems/00/imageBinary/stashtour_highlight_builtforenterprise.png&quot; alt=&quot;Atlassian Stash&quot; /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3 id=&quot;were-still-a-cathedral&quot;&gt;We’re still a cathedral&lt;/h3&gt;

&lt;p&gt;Git decentralises everything, but we’re not a real bazaar: our private code is our cathedral with typical enterprise trends like scrum and kanban in play; and so we have still the need to centralise a lot.
Our list of users and roles we still want centralised, when people push to the master repository are all commits logged against known users or are we going to end up with multiple aliases for every developer? Or worse junk users like “localhost”?
To tackle this we wrote a pre-push hook that authenticates usernames for all commits against &lt;a href=&quot;http://www.atlassian.com/software/crowd/overview&quot;&gt;Crowd&lt;/a&gt;. If a commit from an unknown user is encountered the push fails and the pusher needs to fix their history using this &lt;a href=&quot;https://gist.github.com/carlosmcevilly/2221249&quot;&gt;recipe&lt;/a&gt; before pushing again.&lt;/p&gt;

&lt;p&gt;Releases can be made off any clone and obviously not be something we want. Released artifacts need to be permanent and unique, and deployed to our central maven repository. Maven’s release plugin fortunately tackles this for us as when you run &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;mvn release:prepare&lt;/code&gt; or &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;mvn release:branch&lt;/code&gt; it automatically pushes resulting changes upstream for you, as dictated by the scm details in the pom.xml&lt;/p&gt;

&lt;h3 id=&quot;migrating-repositories&quot;&gt;Migrating repositories&lt;/h3&gt;

&lt;p&gt;Our practice with subversion was to have everything in one large subversion repository, like how Apache does &lt;a href=&quot;http://svn.apache.org/repos/asf/&quot;&gt;it&lt;/a&gt;. This approach worked best for us allowing projects and the code across projects to be freely moved around. With Git it makes more sense for each project to have its own repository, as moving files along with their history between repositories is easy.&lt;/p&gt;

&lt;p&gt;Initial attempts of conversion were using svn2git as described &lt;a href=&quot;http://veys.com/2010/07/24/migrating-multi-project-subversion-repositories-to-git/&quot;&gt;here&lt;/a&gt; along with svndumpfilter3.&lt;/p&gt;

&lt;p&gt;But a plugin in Stash came along called &lt;a href=&quot;http://old.subgit.com/stash/&quot;&gt;SubGit&lt;/a&gt;. It rocks! Converting individual projects from such a large subversion repository one at a time is easy. Remember to moderate the .gitattributes file afterwards, we found in most usecases it could be deleted.&lt;/p&gt;

&lt;h3 id=&quot;hurdles&quot;&gt;Hurdles&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Integration with our existing tools&lt;/strong&gt; (bamboo, fisheye, jira) was easier when everything was in one subversion repository. Now with scores of git repositories it is rather cumbersome. Every new git repository has to be added manually into every other tool. We’re hoping that Atlassian comes to the rescue and provides some sort of &lt;a href=&quot;https://jira.atlassian.com/browse/STASH-2589&quot;&gt;automatic recognition&lt;/a&gt; of new and renamed repositories.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Renaming&lt;/strong&gt; repositories in Stash is very easy, and should be encouraged in an agile world, but it breaks the integration with other tools. Every repository rename means going through other tools and manually updating them. Again we hope Atlassian comes to the rescue.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Binary files&lt;/strong&gt; we were worried about as our largest codebase had many and was already slow on subversion due to it. Subversion also stores all xml files by default as binary and in a large spring based application with a long history this might have been a problem. We were ready to investigate solutions like &lt;a href=&quot;http://git-annex.branchable.com/&quot;&gt;git-annex&lt;/a&gt;. All test migrations though showed that it was not a problem, git clones of this large codebase were super fast, and considerably smaller (subversion 4.1G -&amp;gt; git 1.1G).&lt;/p&gt;

&lt;h3 id=&quot;adaptation&quot;&gt;Adaptation&lt;/h3&gt;

&lt;p&gt;Towards the end of February we were lucky enough to have &lt;a href=&quot;http://twitter.com/tlberglund&quot;&gt;Tim Berglund&lt;/a&gt;, &lt;a href=&quot;http://twitter.com/brntbeer&quot;&gt;Brent Beer&lt;/a&gt;, and &lt;a href=&quot;http://twitter.com/davidgraham&quot;&gt;David Graham&lt;/a&gt;, from GitHub come and &lt;a href=&quot;http://teach.github.com/presentations/git-foundations.html#/&quot;&gt;teach&lt;/a&gt; us Git. The first two days was a set course with 75 participants and covered&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Git Fundamentals (staging, moving, copying, history, branching, merging, resetting, reverting),&lt;/li&gt;
  &lt;li&gt;Collaboration using GitHub (Push, pull, and fetch, Pull Requests Project Sites, Gists, Post-receive hooks), and&lt;/li&gt;
  &lt;li&gt;Advanced Git (Filter-Branch, Bisect, Rebase-onto, External merge/diff tools, Event Hooks, Refspec, .gitattributes).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The third day with the three GitHubbers was more of an open space with under twenty participants where we discussed various &lt;a href=&quot;https://gist.github.com/jarib/184945f80373474943a8&quot;&gt;specifics&lt;/a&gt; to FINN’s adoption to Git, from continuous deployment (which GitHub excels at) to branching workflows.&lt;/p&gt;

&lt;p&gt;No doubt about it this was one of the best, if not very best, courses held for FINN developers, and left everyone with a resounding drive to immediately switch all codebases over to Git.&lt;/p&gt;

&lt;p&gt;Other documentation that’s encouraged for everyone to read/watch is&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;http://git-scm.com/book&quot;&gt;The entire Pro Git book&lt;/a&gt;,&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;http://vimeo.com/49444883&quot;&gt;Advanced Git&lt;/a&gt; video from Tim Berglund,&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;http://www.youtube.com/watch?v=AJ-CpGsCpM0&quot;&gt;The Flow Of Change&lt;/a&gt; video from Google (covers engineering principles of branching codebases).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;img src=&quot;/images/2013-03-20-given-the-git/timberglund.jpg&quot; alt=&quot;Tim Berglund&quot; /&gt;&lt;/p&gt;

&lt;h3 id=&quot;tips-and-tricks-for-beginners&quot;&gt;Tips and tricks for beginners…&lt;/h3&gt;

&lt;p&gt;To wrap it up here’s some of the tips and tricks we’ve documented for ourselves…&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Cant push because HEAD is newer&lt;/strong&gt;
So you pull first… Then you go ahead and push which adds two commits into history: the original and a duplicate merge from you.
You need to learn to do &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;git rebase&lt;/code&gt; in such situations, better yet to do &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;git --rebase pull&lt;/code&gt;.
You can make the latter permanent behaviour with `
    git config –global branch.master.rebase true
    git config –global branch.autosetuprebase always`&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Colour please!&lt;/strong&gt;
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;git config --global color.ui auto&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Did you really want to push commits on all your branches?&lt;/strong&gt;
This can trap people, they often expect push to be restricted to the current branch your on. It can be enforced to be this way with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;git config --global push.default tracking&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Pretty log display&lt;/strong&gt;
Alias &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;git lol&lt;/code&gt; to your preferred log format…
Simple oneline log formatting:
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;git config --global alias.lol &quot;log --abbrev-commit --graph --decorate --all --pretty=oneline&quot;&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Oneline log formatting including committer’s name and relative times for each commit:
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;git config --global alias.lol &quot;log --abbrev-commit --graph --all
--pretty=format:'%Cred%h%Creset -%C(yellow)%d%Creset %s %Cgreen(%cr) %C(bold blue)&amp;lt;%an&amp;gt;%Creset'&quot;&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Compact log formatting with full commit messages, iso timestamps, file history over renames, and mailmap usernames:
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;git config --global alias.lol &quot;log --follow --find-copies-harder --graph  --abbrev=4
--pretty=format:'%Cred%h%Creset -%C(yellow)%d%Creset %Cgreen%ai %n %C(bold blue)%aN%Creset  %B'&quot;&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Cherry-picking referencing the original commit&lt;/strong&gt;
When recording a cherry-pick commit, using the “-x” option appends the line &lt;em&gt;“(cherry picked from commit …)”&lt;/em&gt; to the original commit message so to indicate which commit this change was cherry-picked from. For example &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;git cherry-pick -x 3523dfc&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Quick log to see what i’ve done in the last 24 hours?&lt;/strong&gt;
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;git config --global alias.standup &quot;log --all --since yesterday --author &lt;/code&gt;git config –global user.email&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&quot;&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What files is this project got but ignoring?&lt;/strong&gt;
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;git config --global alias.ignored &quot;ls-files --others --i --exclude-standard&quot;&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Wipe all uncommitted changes&lt;/strong&gt;
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;git config --global alias.wipe &quot;reset --hard HEAD&quot;&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Edit and squash commits before pushing them&lt;/strong&gt;
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;git config --global alias.ready &quot;rebase -i @{u}&quot;&lt;/code&gt;&lt;/p&gt;
</content>
        
        <author>
            <name>mick</name>
            
            <email></email>
            
        </author>
        
    </entry>
    
    <entry>
        <title>I wish I knew my consumers - Maven Reverse Dependency</title>
        <link rel="alternate" type="text/html" href="https://tech.finn.no2013/01/31/i-wish-i-knew-my-consumers-maven-reverse-dependency/"/>
        <updated>2013-01-31T12:49:05+00:00</updated>
        <id>https://tech.finn.no/2013/01/31/i-wish-i-knew-my-consumers-maven-reverse-dependency</id>
        <content type="html">&lt;p&gt;At FINN.no being a developer fixing bugs in a library is a breeze. Getting every user of your library to use the fix, however, is a different story. How to know who to notify? I mean, I know my library’s dependencies, but who “out there” has dependency to the component where I just fixed a bug? I wish. Enter maven-dependency-graph.&lt;/p&gt;

&lt;p&gt;The idea was born on the plane back home from a Copenhagen hosted conference. Graph database. Download neo4j and start dabbling at a maven plugin. Flying time Copenhagen - Oslo was too short, all of a sudden.&lt;/p&gt;

&lt;p&gt;From there, the idea slept for a couple of years. Until the need arose somewhere among the developers. With 100+ different applications running with common core services and libraries, everybody suddenly needed to know who depended on their code which had recently been bugfixed. So the old idea was dusted off and once more saw the light of day. We needed to upgrade the server installation and the API to neo4j - which took some time to grasp; but after some playing around, it became obvious and easy.&lt;/p&gt;

&lt;p&gt;The idea was to have every project report its dependencies to a graph database, building the tree of dependencies on each commit. This constitutes one half of the plugin. Over time, all projects will have reported their dependencies, and from there on part two of the plugin comes into use. It will examine the reverse dependencies to the &lt;em&gt;current&lt;/em&gt; maven project, and report all incoming dependencies to it in the maven log. Hey, presto! We now know who out there uses us! And even which version they are using, thanks to two different keys into the built-in lucene index engine.&lt;/p&gt;

&lt;p&gt;The plugin is published on github @ &lt;a href=&quot;https://github.com/finn-no/maven-dependency-mapper&quot;&gt;Finn Technology’s account&lt;/a&gt;. Feel free!
&lt;a href=&quot;http://twitter.com/gardleopard&quot;&gt;@gardleopard&lt;/a&gt; and &lt;a href=&quot;http://twitter.com/roarjoh&quot;&gt;@roarjoh&lt;/a&gt;&lt;/p&gt;

&lt;h2 id=&quot;usage-examples&quot;&gt;Usage examples&lt;/h2&gt;

&lt;p&gt;Dependencies to current maven project:&lt;/p&gt;
&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;mvn no.finntech:dependency-mapper-maven-plugin:read
[INFO] Scanning for projects...
[INFO]
[INFO] ------------------------------------------------------------------------
[INFO] Building greenpages thrift-client 3.4.5-SNAPSHOT
[INFO] ------------------------------------------------------------------------
[INFO]
[INFO] --- dependency-mapper-maven-plugin:1.0-SNAPSHOT:read (default-cli) @ commons-thrift-client ---
[INFO] Resolving reverse dependencies
[INFO] no.finntech.travel.supplier:supplier-client:1.2-SNAPSHOT -&amp;gt; no.finntech:commons-thrift-client:3.1.1
[INFO] no.finntech.cop:client:1.1-SNAPSHOT -&amp;gt; no.finntech:commons-thrift-client:3.1.1
[INFO] no.finntech.oppdrag-services:iad-model:2013.2-SNAPSHOT -&amp;gt; no.finntech:commons-thrift-client:3.4.3
[INFO] no.finntech:minfinn:2013.2-SNAPSHOT -&amp;gt; no.finntech:commons-thrift-client:3.4.3
[INFO] no.finntech:service-user:2013.2-SNAPSHOT -&amp;gt; no.finntech:commons-thrift-client:3.4.3
[INFO] no.finntech:service-oppdrag:2013.2-SNAPSHOT -&amp;gt; no.finntech:commons-thrift-client:3.4.3
[INFO] no.finntech:kernel:2013.2-SNAPSHOT -&amp;gt; no.finntech:commons-thrift-client:3.4.3
`
(umpteen lines skipped...)
`
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 1.957s
[INFO] Finished at: Thu Jan 31 09:50:19 CET 2013
[INFO] Final Memory: 9M/211M
[INFO] ------------------------------------------------------------------------
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Usage of third party framework (using neo4j’s included admin interface):
&lt;img src=&quot;/images/2013-01-31-i-wish-i-knew-my-consumers-maven-reverse-dependency/neo4jshot.png&quot; alt=&quot;Noe4jshot&quot; /&gt;&lt;/p&gt;
</content>
        
        <author>
            <name>roar</name>
            
        </author>
        
    </entry>
    
    <entry>
        <title>StrataConf &amp; Hadoop World 2012…</title>
        <link rel="alternate" type="text/html" href="https://tech.finn.no2012/11/09/strataconf-hadoop-world-2012/"/>
        <updated>2012-11-09T13:23:06+00:00</updated>
        <id>https://tech.finn.no/2012/11/09/strataconf-hadoop-world-2012</id>
        <content type="html">&lt;p&gt;A summary of this year’s Strataconf &amp;amp; Hadoop World.
A fascinating and inspiring conference with use-cases on both sides of an ethical divide – proof that the technologies coming are game-changers in both our industry and in society. Along with some intimidating use-cases i’ve never seen such recruitment &lt;a href=&quot;http://twitter.com/SQLDiva/status/261523933243789312&quot;&gt;efforts&lt;/a&gt; at any conference before, from multi-nationals to the &lt;a href=&quot;https://twitter.com/comsysto/status/261174163455225857&quot;&gt;CIA&lt;/a&gt;. The need for developers and data scientists in Big Data is burning – the market for Apache Hadoop Market is &lt;a href=&quot;https://twitter.com/TheASF/status/263823598731530240&quot;&gt;expected&lt;/a&gt; to reach $14 billion by 2017.&lt;/p&gt;

&lt;p&gt;Plenty of honesty towards the hype and the challenges involved too. A barcamp &lt;em&gt;Big Data Controversies&lt;/em&gt; labelled it all as Big Noise and looked at ways through the hype. It presented balancing perspectives from a insurance company’s statistician who has dealt successfully with the problem of too much data for a decade and a hadoop techie who could provide much desired answers to previously impossible questions. Highlights from this barcamp were…&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;
    &lt;p&gt;One should always use intelligent samples before ever committing to big data.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;Unix tools can be used but they are not very fault tolerant.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;You know when you’re storing too much denormalised data when you’re also getting high compression rates on it.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;MapReduce isn’t everything as it can be replaced with indexing.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;If you try to throw automated algorithms at problems without any human intervention you’re bound to bullshit.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;Ops hate hadoop and this needs to change.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;Respecting user privacy is important and requires a culture of honesty and common-sense within the company. But everyone needs to understand what’s illegal and why.&lt;/p&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Noteworthy (10 minute) keynotes…&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;*
 &lt;em&gt;&lt;a href=&quot;http://bit.ly/SJGrHf&quot;&gt;The End of the Data Warehouse&lt;/a&gt;&lt;/em&gt;. They are monuments to the old way of doing things: pretty packaging but failing to deliver the business value. But Hadoop too is still flawed…  Also a &lt;a href=&quot;http://bit.ly/RijQBv&quot;&gt;blog&lt;/a&gt; available.&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;em&gt;&lt;a href=&quot;http://bit.ly/R6bkFS&quot;&gt;Moneyball for New York City&lt;/a&gt;&lt;/em&gt;. How NYC council started combining datasets from different departments with surprising results.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;em&gt;&lt;a href=&quot;http://bit.ly/Pwgj4y&quot;&gt;The Composite Database&lt;/a&gt;&lt;/em&gt;, a focus on using big data for product development. To an application programmer the concept of a database is moving from one entity into a componential architecture.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;em&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=00RgRJIAxYo&quot;&gt;Bringing the ‘So What’ to Big Data&lt;/a&gt;&lt;/em&gt;, a different keynote with a sell towards going to work for the CIA. Big data isn’t about data but changing and improving lives.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;em&gt;&lt;a href=&quot;http://bit.ly/PPVB15&quot;&gt;Cloud, Mobile and Big Data&lt;/a&gt;&lt;/em&gt;. Paul Kent, a witty speaker, talks about analytics in a new world. “At the end of the day, we are closer to the beginning than we are at end of this big data revolution… One radical change hadoop and m/r brings is now we push the work to the data, instead of pulling the data out.”&lt;/p&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Noteworthy (30 minute) presentations…&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;*
 &lt;em&gt;&lt;a href=&quot;http://slidesha.re/PAXdu2&quot;&gt;The Future – Hadoop-2&lt;/a&gt;&lt;/em&gt;. Hadoop YARN makes all functional programming algorithms possible, reducing the existing map reduce framework to just one of many user-land libraries. Many existing limitations are removed. Fault-tolerance is improved (namenode). 2x performance improvement on small jobs.&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;em&gt;&lt;a href=&quot;http://tech.finn.no/?attachment_id=1758&quot;&gt;Designing Hadoop for the Enterprise Data Center&lt;/a&gt;&lt;/em&gt;. A joint talk from Cisco and Cloudera on hardware tuning to meet Hadoop’s serious demands. 10G networks help. dual-attached 1G networks is an alternative. More jobs in parallel will average out network bursts. Data-locality misses hurt network, consider above a 80% data-locality hitrate good.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;em&gt;&lt;a href=&quot;http://slidesha.re/YraGqG&quot;&gt;How to Win Friends and Influence People&lt;/a&gt;&lt;/em&gt;. LinkedIn presents four of their big data products
∘ Year in Review. Most successful email ever – 20% response rate.
∘ Network Updates.
∘ Skills and Endorsements. A combination of propensity to know someone and the propensity to have the skill.
∘ People You May Know. Listing is easy, but ranking required combining many datasets. 
All these products were written in PIG. Moving data around is the key problem. Kafka is used instead of scribe.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;em&gt;&lt;a href=&quot;http://bit.ly/UaxOdm&quot;&gt;Designing for data-driven organisation&lt;/a&gt;&lt;/em&gt;. Many companies who think they are data-driven are in fact metrics-driven. It’s not the same thing. Metrics-driven companies often want interfaces with less data. Data-driven companies have data rich interfaces presenting holistic visualisations.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;em&gt;&lt;a href=&quot;http://slidesha.re/RzPWuk&quot;&gt;Visualizing Networks&lt;/a&gt;&lt;/em&gt;. The art of using the correct visualisation and layout. Be careful of our natural human trait to see visual implications from familiarity and proximity – we don’t always look at the legend. A lot of examples using the d3 javascript library.&lt;/p&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The two training sessions I attended were Testing Hadoop, and Hadoop using Hive.
&lt;em&gt;&lt;a href=&quot;http://bit.ly/XbVbDK&quot;&gt;Testing Hadoop.&lt;/a&gt;&lt;/em&gt;
Presented by an old accomplice from the NetBeans Governance board, Tom Wheeler. He presented an interesting perspective on testing calling it another form of computer security: “a computer is secure if you can depend on it and its software to behave as you expect”. Otherwise i took home a number of key technologies to fill in the gaps between and around our current unit and single-node integration tests on our CountStatistics project: Apache MRUnit for m/r units, MiniMRCluster and MiniDFSCluster for multi-jvm integration cluster, and BigTop for ecosystem testing (pig, hive, etc). We also went through various ways to benchmark hadoop jobs using TeraSort, MRBench, NNBench, TestDFSIO, GridMix3, and SWIM. Lastly we went through a demo of the free product “Cloudera Manager” – a diagnostics UI similar to Cassandra’s OpsCenter.&lt;/p&gt;

&lt;p&gt;Hadoop using Hive.
Hive provides an SQL interface to Hadoop. It works out-of-the-box if you’re using HBase but with Cassandra as our underlying database we haven’t gotten around to installing it yet. The tutorial went through many live queries on a AWS EC2 cluster, exploring the specifics to STRUCTs in schemas, JOINs, UDFs, and serdes. This is a crucial interface to make it easier for others, particularly BI and FINN økosystem, to explore freely through our data. Pig isn’t the easiest way in for outsiders, but everyone knows enough SQL to get started. Fingers crossed we get Hive or Impala installed some time soon…&lt;/p&gt;

&lt;p&gt;A number of meet-ups occurred during the evenings, one hosted at AppNexus, a company providing 10 billion real-time ads per day (with a stunning office). AppNexus does all their hadoop work using python, but they also putting focus on RESTful Open APIs like we do. The other meetup represented Cassandra by &lt;a href=&quot;http://www.datastax.com/&quot;&gt;DataStax&lt;/a&gt; with plenty of free Cassandra beer. Latest &lt;a href=&quot;http://bit.ly/Ut7ZzB&quot;&gt;benchmarks&lt;/a&gt; prove it to be the fastest distributed database around. I was hoping to see more Cassandra at strataconf – when someone mentions big data i think of Cassandra before Hadoop.&lt;/p&gt;

&lt;p&gt;Otherwise this US election was on the news as the &lt;em&gt;&lt;a href=&quot;http://bit.ly/RCFWim&quot;&gt;big data election&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/2012-11-09-strataconf-hadoop-world-2012/hadoop1.jpg&quot; alt=&quot;Hadoop&quot; /&gt;&lt;/p&gt;
</content>
        
        <author>
            <name>mick</name>
            
            <email></email>
            
        </author>
        
    </entry>
    
    <entry>
        <title>Leaving the Tower of Babel</title>
        <link rel="alternate" type="text/html" href="https://tech.finn.no2012/09/04/leaving-the-tower-of-babel/"/>
        <updated>2012-09-04T09:48:05+00:00</updated>
        <id>https://tech.finn.no/2012/09/04/leaving-the-tower-of-babel</id>
        <content type="html">&lt;p&gt;Here at FINN we use mostly Java for our day-to-day work, but we do have some applications and modules written in other languages. We currently have code in Java, Ruby, JavaScript, Objective-C and Scala, supporting things as diverse as testing frameworks and iOS-apps.&lt;/p&gt;

&lt;p&gt;Recently there has been some discussion with regards to what we should be using for new projects, as each team of developers start suggesting that they should use something they think would be easier and more effective. The arguments against are based on how easy it would be to recruit new developers who know these new technologies, how well it would perform under our kinds of loads, and wheter the assumption that we can be more effective is actually true.&lt;/p&gt;

&lt;p&gt;Our strategy is firm on this point, that experiments can be done, but for major applications serving the public, we should be using Java for the time being. We are however considering if it’s time to take a second look at our chosen languages, and see if we should expand our portfolio. This strategy is founded on a wish to reduce the size of our technology portfolio, so that we don’t have to spend time and effort to bring developers up to speed when they switch teams. There’s also a higher chance that new hires will be familiar with Java, while other technologies would often require a period of training when they first start.&lt;/p&gt;

&lt;p&gt;During these discussions, we created a quick poll and sent it out on our internal discussionboard. The poll asked questions like “How long have you worked as a developer?”, “Which language do you use for your day-to-day work?”, “If you were free to chose, which language would you use for your next project?” and “Which programming languages do you know?”. Our definition of “know” was very open in this poll, lowering the bar to allow people who had maybe written a single example program, and understands a bit of code could check the box.&lt;/p&gt;

&lt;p&gt;Out of the nearly 100 people that work with development, 56 answered. We can assume that the people who did answer, were the people who were interested in the topic, and might not be a representative selection, but the results are interesting none the less.&lt;/p&gt;

&lt;p&gt;So.. what did we learn?&lt;/p&gt;

&lt;h3 id=&quot;experience&quot;&gt;Experience&lt;/h3&gt;

&lt;p&gt;Being a poll made in the midst of a discussion, mostly for fun, this part of the poll wasn’t framed in a way that gives us much useful information. We can say that the average developer who answered has worked for around seven or eight years, but with what looks like a reasonably fair spread across all groups. Almost as many with a couple years experience, as there are people with 10 to 15 years.&lt;/p&gt;

&lt;h3 id=&quot;primary-language-in-day-to-day-work&quot;&gt;Primary language in day-to-day work&lt;/h3&gt;

&lt;p&gt;Being a predominatly Java shop, most of the respondents were using Java for their daily work. 35 out of 56 were Java-developers. 11 respondents worked with JavaScript daily, while four worked with Scala, three with Ruby, two with Objective-C, and finally one database-developer who worked mostly in T-SQL (The SQL-variant used in Sybase).&lt;/p&gt;

&lt;h3 id=&quot;most-popular-choice-if-allowed-to-chose-freely&quot;&gt;Most popular choice if allowed to chose freely&lt;/h3&gt;

&lt;p&gt;The most interesting part of the poll, with many interesting insights. This is also the most loaded part, as the results could easily short-circuit any discussion about future choice in language.&lt;/p&gt;

&lt;p&gt;The bad news (or good, depending on your point of view), is that there was no clear answer from this section. Keep in mind that around 40 people didn’t answer this poll, and could be assumed to be content with the current status-quo.&lt;/p&gt;

&lt;p&gt;The biggest group was Java, not surprisingly. The surprising part is that it was only 16 people who would chose Java if they could chose freely. This is much lower than expected, but still make up the largest group. Of these, 13 people are already using Java today. 20 Java developers would like to use something else.&lt;/p&gt;

&lt;p&gt;So, if Java was the largest group, at 16 people, what does that mean for the remainder of the results? Well, 10 people would have chosen Scala, while JavaScript, Ruby, Clojure and Groovy clock in at about four or five respondents wanting to use them. At the bottom of the pack we have Python and Objective-C with three and two respondents chosing them.&lt;/p&gt;

&lt;p&gt;Six people were interested enough to answer the poll, but chose the non-commitant “I don’t care, as long as I’m making good stuff for our users” option.&lt;/p&gt;

&lt;p&gt;The number of possible answers here was a rather large selection of popular and not-so-popular-but-quite-well-known languages, so the fact that the list only includes eight languages is a sign that it’s not a completely random selection. Still, quite a lot of discussion is needed before any one of those languages gets center stage.&lt;/p&gt;

&lt;p&gt;A couple curious details found in this part of the poll includes the fact that the only people who would chose JavaScript are people who are already working in JavaScript every day. Groovy, Clojure, Objective-C and Scala have all managed to be chosen by a person who doesn’t actually know the langauge. There’s only three people who would switch &lt;em&gt;to&lt;/em&gt; Java, if allowed to chose.&lt;/p&gt;

&lt;h3 id=&quot;which-languages-do-we-know&quot;&gt;Which languages do we “know”?&lt;/h3&gt;

&lt;p&gt;As mentioned earlier, the bar for “knowing” a language was set quite low in this poll, to get more diversity in answers. This resulted in some interesting numbers.&lt;/p&gt;

&lt;p&gt;Being primarily a Java-shop, it might not surprise anyone that a full 100% knows Java. A little over three fourths know JavaScript, while Ruby and T-SQL was known to about half. These are the main languages most of us have some sort of dealings with in our daily work, so that they score high is not surprising.&lt;/p&gt;

&lt;p&gt;Next on the list is PHP and Python, with around 40% knowing them.&lt;/p&gt;

&lt;p&gt;We run primarily Sybase for our databases, with a small number of MySQL and PostgreSQL servers for smaller applications and modules. We are trying to standardise on PostgreSQL, but this isn’t reflected in knowledge, with 37.5% knowing how to code MySQL-procedures, 35.7% knowing how to code for Oracle, and only 25% knowing their way around a PostgreSQL codebase.&lt;/p&gt;

&lt;p&gt;Our operations-department have had a “vendetta” against Perl for several years, but there are still more people who knows Perl than there are people who knows Scala, with the score 18 to 15. Groovy has quite a following too, with 13 respondents knowing Groovy.&lt;/p&gt;

&lt;p&gt;We have a small Apps team working with iOS-development, but Objective-C has a reach far outside that team, with 10 respondents knowing Objective-C.&lt;/p&gt;

&lt;p&gt;Common Lisp is known to five respondents, while Clojure suprisingly was only known to seven respondents. As you can read elsewhere in this blog, we had a Clojure workshop at our Technology Day earlier this summer, and these results might be telling us something about the language or our teachers when it didn’t have a better ability to “stick” than this.&lt;/p&gt;

&lt;p&gt;We also have people who know some Erlang, Smalltalk, Lua, Haskell, Scheme, Eiffel and ML/SML.&lt;/p&gt;

&lt;h3 id=&quot;how-many-languages-do-we-know&quot;&gt;How many languages do we “know”?&lt;/h3&gt;

&lt;p&gt;On average, we know 6.8 languages each. The most knowledgeable person knows 16 languages, while the least knowledgeable knows only one. The high experience respondents, 16 years or more, have a higher average than the rest of us, with 9.4, while the rest of the “experience brackets” know somewhere between six and seven languages.&lt;/p&gt;

&lt;p&gt;Several programmer-gurus seem to think you should stribe to teach yourself one new language every year, and it would seem atleast one of our respondents have been able to follow this advice. For those of us with less extra time, a less ambitious strategy might be more compatible, but that we should stribe to learn something new every once in a while seems to be good advice.&lt;/p&gt;

&lt;h3 id=&quot;now-what&quot;&gt;Now what?&lt;/h3&gt;

&lt;p&gt;As mentioned previously, this poll was not a serious attemt at gaining actionable insights. The results should be taken with a large helping of salt, and at most used as a basis for discussions. On the other hand, I know we will be discussing this topic going forward, because the fact that only 16 of 56 wanted to use Java is telling us it’s time to start the discussion. There are possibly 40 developers who would prefer Java, who didn’t respond, so it’s too early to draw conclusions, but we have a place to start.&lt;/p&gt;

&lt;p&gt;There are other questions falling out of this too:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;
    &lt;p&gt;There are two developers who wants to work with Objective-C, and eight Java-developers who wants to work with Scala, so why do we have so few internal applicants when we have openings on the teams working with these languages?&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;What is it that makes developers want to work with languages they don’t even know?&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;Of the respondents, more than half the Java-developers wants to use something else. Why is that?&lt;/p&gt;
  &lt;/li&gt;
&lt;/ul&gt;

</content>
        
        <author>
            <name>Morten Lied Johansen</name>
            
            <email></email>
            
        </author>
        
    </entry>
    
    <entry>
        <title>Foraging in the landscape of Big Data</title>
        <link rel="alternate" type="text/html" href="https://tech.finn.no2012/08/06/foraging-in-the-landscape-of-big-data/"/>
        <updated>2012-08-06T10:26:00+00:00</updated>
        <id>https://tech.finn.no/2012/08/06/foraging-in-the-landscape-of-big-data</id>
        <content type="html">&lt;p&gt;&lt;span class=&quot;image-wrap&quot; style=&quot;float: right;&quot;&gt;&lt;img style=&quot;margin: 5px; border: 0px solid black;&quot; src=&quot;/images/2012-08-06-foraging-in-the-landscape-of-big-data/salamander.gif&quot; alt=&quot;Salamander&quot; /&gt; &lt;/span&gt;
This is the first article describing FINN.no’s coming of age with Big Data. It’ll cover the challenges arising the need for it, the challenges in accomplishing new goals with new tools, and the challenges that remain ahead.&lt;/p&gt;

&lt;p&gt;Big Data is for many just another vague and hyped up trend getting more than its far share of attention. The general definition, from &lt;a href=&quot;http://en.wikipedia.org/wiki/Big_data&quot;&gt;Wikipedia&lt;/a&gt;, is big data covers the scenario where existing tools fail to process the increasing amount or dimensions of data. This can mean anything from:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;α&lt;/strong&gt; – the existing tools being poor &lt;em style=&quot;font-size: 80%;&quot;&gt;(while large companies pour $$$ into scaling existing solutions up)&lt;/em&gt; or&lt;br /&gt;
&lt;strong&gt;β&lt;/strong&gt; – the status quo requiring more data to be used, to&lt;br /&gt;
&lt;strong&gt;γ&lt;/strong&gt; – a requirement for faster and more connected processing and analysis on existing data.&lt;/p&gt;

&lt;p&gt;&lt;br /&gt;&lt;span class=&quot;image-wrap&quot; style=&quot;float: left;&quot;&gt;&lt;a href=&quot;http://navajonationparks.org&quot;&gt;&lt;img style=&quot;margin-right: 10px; border: 0px solid black;&quot; title=&quot;http://navajonationparks.org&quot; src=&quot;/images/2012-08-06-foraging-in-the-landscape-of-big-data/21603273182394671_O5rG91sY_f-e1344176794222.jpg&quot; alt=&quot;http://navajonationparks.org&quot; /&gt;&lt;/a&gt;&lt;/span&gt;The latter two are also described as big data’s three V’s: Volume, Variety, Velocity. If the theoretical definition isn’t convincing you put it into context against some of today’s big data crunching use-cases…&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;online advertising combining content analysis with behavioural targeting,&lt;/li&gt;
  &lt;li&gt;biomedical’s DNA sequence tagging,&lt;/li&gt;
  &lt;li&gt;pharmaceutics’s metabolic network modelling,&lt;/li&gt;
  &lt;li&gt;health services detecting disease/virus spread via internet activity &amp;amp; patient records,&lt;/li&gt;
  &lt;li&gt;the financial industry ranging from credit scores at retail level to quant trading,&lt;/li&gt;
  &lt;li&gt;insurance companies crunching actuarial data,&lt;/li&gt;
  &lt;li&gt;US defence programs for offline (ADAMS) and online (CINDER) threat detection,&lt;/li&gt;
  &lt;li&gt;environmental research into climate change and atmospheric modelling, and&lt;/li&gt;
  &lt;li&gt;neuroscience research into mapping the human brain’s neural pathways.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;br /&gt;On the other hand big data is definitely no silver bullet. It cannot give you answers to questions you haven’t yet formulated (pattern recognition aside), and so it doesn’t give one excuses to store overwhelming amounts of data where the potential value in it is still &lt;a href=&quot;http://seekingalpha.com/article/441171-beware-the-hype-over-big-data-analytics&quot;&gt;undefined&lt;/a&gt;. And it certainly won’t make analysis of existing data sets initially any easier. In this regard it’s less to do with the difficulty of achieving such tasks and more to do with the potential to solve what was previously impossible.&lt;/p&gt;

&lt;p&gt;Often companies can choose their direction and the services they will provide, but in any competitive market where one fails to match the competitor’s offerings it can result in the fall of that company. Here Big Data earns its hype with many a CEO concerned into paying attention. And it probably gives many CEOs a headache as the possibilities it opens, albeit tempting or necessary, create significant challenges in and of themselves. The multiple vague dimensions of big data also allows the critics plenty of room to manoeuvre.&lt;/p&gt;

&lt;p&gt;One can argue that to &lt;a href=&quot;http://www.youtube.com/watch?v=zAbFRiyT3LU&quot;&gt;scale up&lt;/a&gt;: to buy more powerful machines or to buy more expensive software solves the problem (α). If all you have is this problem then sure it’s a satisfactory solution. But ask yourself are you successfully solving today’s problem while forgetting your future?&lt;/p&gt;
&lt;blockquote&gt;“If we look at the path, we do not see the sky.” – Native American saying&lt;/blockquote&gt;
&lt;p&gt;One can also argue away the need for such vasts amounts of data (β). Through various strategies: more aggressive normalisation of the data, storing data for shorter periods, or persisting data in more ways in different places; the size of each individual data set can be significantly reduced. Excessively normalising data has its benefits and is what one may do in the Lean development approach for any new feature or product. Indeed a simpler datamodel trickles through into a simpler application design, in turn leaving more content, more productive, pragmatic developers. Nothing to be scoffed at in the slightest. But in this context the Lean methodology isn’t about any one state or snapshot in time illustrating the simple and the minimalistic, rather it’s about evolution, the processes involved and their direction. Much like the KISS saying: it’s not about doing it simple stupid but about &lt;em&gt;keeping&lt;/em&gt; it simple stupid. Here it questions to how does overly normalised data evolve as its product becomes successful and something more complicated over time. Anyone who has had to deal awkwardly with numerous and superfluous tables, joins, and indexes in a legacy application because it failed over time to continually improve its datamodel due to needs of compatibility knows what we’re talking about. There is another problem from such legacy applications that follow a datamodel centric design: the datamodel itself becomes a public API and the many consumers of it create this need of compatibility and the resulting inflexible datamodel. But this isn’t an underlying but rather overlapping problem as one loses oversight of the datamodel in its completeness and in a way represented optimally.&lt;/p&gt;

&lt;p&gt;&lt;span class=&quot;image-wrap&quot; style=&quot;float: left;&quot;&gt;
&lt;img style=&quot;margin-right: 15px; border: 0px solid black;&quot; src=&quot;/images/2012-08-06-foraging-in-the-landscape-of-big-data/big-data-300x225.jpg&quot; alt=&quot;Big data&quot; width=&quot;200&quot; /&gt;&lt;br /&gt;&lt;br /&gt;&lt;/span&gt;It also difficult to deny the amount of data we’re drowning in today.
“90% of the data that exists today was created in the last two years.. the sheer volume of social media and mobile data streaming in daily, businesses expect to use big data to aggregate all of this data, extract information from it, and identify value to clients and consumers.. Data is no longer from our normalised datasets sitting in our traditional databases. We’re accessing broader, possibly external, sources of data like social media or learning to analyse new types of data like image, video, and audio..” – greenbookblog.org.&lt;/p&gt;

&lt;p&gt;And one may also argue that existing business intelligence solutions (can) provide the analysis required from all already existing datasets (γ). It ignores a future of possibilities: take for example the research going into behavioural targeting giving glimpses into the challenges of modern marketing as events and trends spark, shift, and evolve through online social media with ever faster frequencies – just the tip of the iceberg when one thinks forward to being able to connect face and voice recognition to emotional pattern matching analysis. But it also defaults to the conservative opinion that business intelligence need be but a post-mortem analysis of events and trends so to provide the insights intended and required only for internal review. This notion that this large scale analysis is of sole benefit to company strategy must become the tell tale of companies failing to see how users and the likes of social media change dramatically what is possible in product development today.&lt;/p&gt;

&lt;p&gt;The methodologies of innovation therefore change. Real time analysis of user behaviour plays a forefront role in decisive actions on what product features and interfaces will become successful. This is the potential to cut down the risk of product development’s internal guesswork for a product’s popularity at any given point in time. In turn this cuts down time-to-market bringing the products release date closer to its &lt;em&gt;maximum popularity potential moment&lt;/em&gt;. Startup companies know that a success doesn’t come only from a clever designed product, there is a significant factor of luck involved in releasing the right product at the right time. Large, and very costly, marketing campaigns can only extend, or synthetically create, this potential moment by so much.&lt;/p&gt;

&lt;p&gt;This latter point around the extents and performance of big data analysis and the differentiation it creates between business analysis versus richer, more spontaneous, product development and innovation creates for FINN an important and consequential factor to our forage into big data. Here at FINN a product owner recently said: “FINN is already developing its product largely built around the customer feedback and therefore achieving &lt;a href=&quot;http://en.wikipedia.org/wiki/Lean_Startup&quot;&gt;continuous innovation&lt;/a&gt;?”. Of course what he meant to say was “from numbers we choose to collect, we generate the statistics and reports we think are interesting, and from these statistics we freely interpret them to support the hypothesis we need for further product development…” We couldn’t be further from continuous innovation if it hitched a lift to the other side of Distortionville&lt;a href=&quot;http://www.mariettatimes.com/page/content.detail/id/533694/Assessment-couldn-t-be-further-from-the-truth.html?nav=5007&quot;&gt;¹&lt;/a&gt;.&lt;/p&gt;
&lt;blockquote&gt;“It is a capital mistake to theorize before one has data. Insensibly one begins to twist facts to suit theories, instead of theories to suit facts” – Arthur Conan Doyle&lt;/blockquote&gt;

&lt;p&gt;FINN isn’t alone here, I’d say most established companies while trying to brand themselves as innovative are still struggling to break out of their existing models of product development based upon internal business analysis. No one said continuous innovation was easy, and there’s a lot of opinions out on this, but along with shorter deployment cycles i reckon there’s two keywords: truth and transparency. Tell your users the truth and watch how they respond. For example give them the statistics that show them their ads stop getting visits after a week, and then observe how they behave, to which solution do they flock to regain traffic to their ads. Don’t try and solve all their problems for them, rather try to enable them. You’ll probably make more money out of them, and by telling them the truth you’ve removed a vulnerability, or to what some fancy refers to as “a business secret”.&lt;/p&gt;

&lt;p&gt;There’s also a potential problem with organisational silos. Large companies having invested properly in business intelligence and data warehousing will have assigned these roles of data collection, aggregation, and analysis, to a separate team or group of experts typically trained database administrators, statisticians, and traffic analysts. They are rarely the programmers, the programmers are on the front lines building the product. Such a split can parallel the sql vs &lt;a href=&quot;http://www.slideshare.net/emileifrem/nosql-overviewneo4jintrotriforkgeeknighttoslideshare&quot;&gt;nosql&lt;/a&gt; camps. This split against the programmers whom you rely on to make continuous innovation a reality can run the risk of stifling any adoption of big data. With the tools enabling big data the programmers can generate reports and numbers previously only capable from the business intelligence and data warehousing departments, and can serve them to your users at web response times – integrating such insights and intelligence into your product. Such new capabilities &lt;a href=&quot;http://radar.oreilly.com/2011/01/data-warehouse-big-data.html&quot;&gt;doesn’t supersede&lt;/a&gt; these traditional departments, rather it needs everyone accepting the new: working together to face new challenges with old wisdom. The programmers working on big data, even if tools and data become shared between these two organisational silos, cannot replace the needs of business intelligence any more than business intelligence can undertake big data’s potential. As data and data sources continue to increase year after year the job of asking the right questions, even knowing how to formulate the questions correctly, needs all hands on deck. Expecting your programmers to do it all might well swamped them into oblivion, but it isn’t just the enormity of new challenges involved, it’s that these challenges have an integral nature to them that programmers aren’t typically &lt;a href=&quot;http://management.fortune.cnn.com/2012/03/19/big-data-wont-solve-your-companys-problems/&quot;&gt;trained to tackle&lt;/a&gt;. Big Data can be used as an opportunity not only to introduce exciting new tools, paradigms, and potential into the company but as a way to help remove existing organisational silos.&lt;/p&gt;

&lt;p&gt;The need for big data at FINN came from a combination of (α) and (γ). &lt;span class=&quot;image-wrap&quot; style=&quot;float: left;&quot;&gt;&lt;img style=&quot;margin: 5px; border: 0px solid black;&quot; src=&quot;/images/2012-08-06-foraging-in-the-landscape-of-big-data/salamander.gif&quot; alt=&quot;Salamander&quot; /&gt; &lt;/span&gt; The statistics we show users for their ads had traditional been accumulated and stored in a sybase table. These statistics included everything from page views, “tip a friend” emails sent, clicks on promoted advertisement placements, ads marked as favourite, and whatever else you can imagine.&lt;/p&gt;

&lt;p&gt;(α) FINN is a busy site, the busiest in Norway, and we display ~50 million ad pages each day. Like a lot of web applications we had a modern scalable presentation and logic &lt;a href=&quot;http://en.wikipedia.org/wiki/Multitier_architecture&quot;&gt;tier&lt;/a&gt; based upon ten tomcat servers but just one not-so-scalable monster database sitting in the data tier. The sybase procedure responsible for writing to the statistics table ended up being our biggest thorn. It used 50% of the database’s write execution time, 20% of total procedure execution time, and the overall load it created on the database accounted for 30% of ad page performance. It was a problem we had lived with long enough that Operations knew quickly to turn off the procedure if the database showed any signs of trouble. Over one period of troublesome months Operations wrote a cronjob to turn off the procedure automatically during peak traffic hours – when ads were receiving the most traffic we had to stop counting altogether, embarrassing to say the least!&lt;/p&gt;

&lt;p&gt;(γ) On top of this product owners in FINN had for years been requesting that we provide statistics on a day basis. The existing table had tinkered with this idea for some of the numbers that didn’t accumulate so high, eg “tip a friend emails”, but for page viewings this was completely out of the question – not even the accumulated totals were working properly.&lt;/p&gt;

&lt;p&gt;At the time we were in the process of modularising the FINN web application. The time was right to turn statistics into something modern and modular. We wanted an asynchronous, fault-tolerance, linearly scaling, and durable solution. The new design uses the Command-Query Separation pattern by using two separate modules: one for counting and one for displaying statistics. The counting achieves asynchronousity, scalability, and durability by using Scribe. The backend persistence and statistics module achieves all goals by using Cassandra and Thrift. As an extension of the &lt;a href=&quot;http://highscalability.com/blog/2009/10/13/why-are-facebook-digg-and-twitter-so-hard-to-scale.html&quot;&gt;push-on-change&lt;/a&gt; model: the counting stores denormalised data and it is later normalised to the views the statistics module requires; we use MapReduce jobs within a Hadoop cluster.&lt;/p&gt;

&lt;p&gt;&lt;span class=&quot;image-wrap&quot; style=&quot;float: left;&quot;&gt;&lt;img style=&quot;margin: 5px; border: 0px solid black;&quot; src=&quot;/images/2012-08-06-foraging-in-the-landscape-of-big-data/2892558385_69bb7f4465.jpg&quot; alt=&quot;http://www.flickr.com/photos/ciordia/2892558385/sizes/m/in/photostream/&quot; /&gt; &lt;/span&gt;&lt;/p&gt;

&lt;p&gt;The resulting project is kick-ass, let me say. Especially Cassandra, it is a truly amazing modern database: linear scalability, decentralised, elastic, fault-tolerant, durable; with a rich datamodel that provides often &lt;a href=&quot;http://maxgrinev.com/2010/07/12/do-you-really-need-sql-to-do-it-all-in-cassandra/&quot;&gt;superior&lt;/a&gt; approaches to joins, grouping, and ordering than traditional sql. But we’ll spend more time describing the project in technical details in a later article.&lt;/p&gt;

&lt;p&gt;A challenge we face now is broader adoption of the project and the technologies involved. Various departments: from developers to tech support; want to read the data, regardless if it is traditional or ‘big data’, and the habit was always to read it directly from production’s Sybase. And it’s a habit that’s important in fostering a data-driven culture within the company, without having to encourage datamodel centric designs. With our Big Data solution this hasn’t been so easy. Without this transparency to the data, developers, tech support, and product owners alike seem to be failing to initiate further involvement - to solve this, since our big data is stored in Cassandra, we’d love to see a read only web-based gui interface based off caqel. …to be continued…&lt;/p&gt;

&lt;h2 id=&quot;references&quot;&gt;References:&lt;/h2&gt;
&lt;p&gt;• &lt;a href=&quot;http://www.fastcoexist.com/1679847/an-operating-system-for-dna&quot;&gt;DNA sequencing is ultimately a big data problem.&lt;/a&gt;
• &lt;a href=&quot;http://www.whitehouse.gov/sites/default/files/microsites/ostp/big_data_fact_sheet.pdf&quot;&gt;Obama’s 2012 Big Data initiatives&lt;/a&gt;
• &lt;a href=&quot;http://seekingalpha.com/article/441171-beware-the-hype-over-big-data-analytics&quot;&gt;Beware The Hype Over Big Data Analytics&lt;/a&gt;
• &lt;a href=&quot;http://management.fortune.cnn.com/2012/03/19/big-data-wont-solve-your-companys-problems/&quot;&gt;Big Data won’t solve your company’s problems&lt;/a&gt;
• Big Data: Opportunity or Threat for Market Research?
• &lt;a href=&quot;http://www.youtube.com/watch?v=zAbFRiyT3LU&quot;&gt;O’Reilly MySQL CE 2010: Mark Atwood, “Guide to NoSQL, redux” &lt;/a&gt;
• &lt;a href=&quot;http://en.wikipedia.org/wiki/Lean_Startup&quot;&gt;Lean Startup&lt;/a&gt;
• &lt;a href=&quot;http://tech.finn.no/2011/05/09/putting-a-face-on-quality/&quot;&gt;FINN moving toward continuous innovation&lt;/a&gt;
• &lt;a href=&quot;http://radar.oreilly.com/2011/01/data-warehouse-big-data.html&quot;&gt;Will data warehousing survive the advent of big data?&lt;/a&gt;
• &lt;a href=&quot;http://highscalability.com/blog/2009/10/13/why-are-facebook-digg-and-twitter-so-hard-to-scale.html&quot;&gt;Why are Facebook, Digg, and Twitter so hard to scale?&lt;/a&gt;
• &lt;a href=&quot;http://maxgrinev.com/2010/07/12/do-you-really-need-sql-to-do-it-all-in-cassandra/&quot;&gt;Do You Really Need SQL to Do It All in Cassandra?&lt;/a&gt;&lt;/p&gt;
</content>
        
        <author>
            <name>mick</name>
            
            <email></email>
            
        </author>
        
    </entry>
    
    <entry>
        <title>the ultimate view — Tiles-3</title>
        <link rel="alternate" type="text/html" href="https://tech.finn.no2012/07/25/the-ultimate-view-tiles-3/"/>
        <updated>2012-07-25T13:48:52+00:00</updated>
        <id>https://tech.finn.no/2012/07/25/the-ultimate-view-tiles-3</id>
        <content type="html">&lt;p&gt;A story of getting the View layer up and running quickly in Spring…&lt;/p&gt;

&lt;blockquote&gt;Since the &lt;a href=&quot;http://tech.finn.no/2010/11/04/the-ultimate-view/&quot;&gt;original article&lt;/a&gt;, parts of the code has been accepted upstream, now available as part of the Tiles-3 release, so the article has been updated — it's all even simpler!&lt;/blockquote&gt;

&lt;div style=&quot;font-size:80%;&quot;&gt;

&lt;div style=&quot;background-color: WhiteSmoke;&quot;&gt;Based upon the &lt;strong&gt;Composite pattern&lt;/strong&gt; and &lt;strong&gt;Convention over Configuration&lt;/strong&gt; we'll pump steroids into
&lt;br /&gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;a web application's view layer
&lt;br /&gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;with four simple steps using &lt;strong&gt;Spring&lt;/strong&gt; and &lt;strong&gt;Tiles-3&lt;/strong&gt;
&lt;br /&gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;to make organising large complex websites elegant with minimal of xml editing.&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;

&lt;p&gt;&lt;span class=&quot;image-wrap&quot; style=&quot;float: right&quot;&gt;&lt;img src=&quot;/images/2012-07-25-the-ultimate-view-tiles-3/1351-222x300.jpg&quot; alt=&quot;tiles&quot; /&gt;&lt;/span&gt;
&lt;br /&gt;&lt;/p&gt;

&lt;p&gt;Contents of article…
 • Background
 • Step 0: Spring to Tiles Integration
 • Step 1: Wildcards
 • Step 2: The fallback pattern
 • Step 3: Definition includes
 • When the Composite pattern is superior
 • Conclusion&lt;/p&gt;

&lt;p&gt;&lt;br /&gt;&lt;/p&gt;

&lt;h2 id=&quot;background&quot;&gt;Background&lt;/h2&gt;

&lt;div style=&quot;font-size:90%;&quot;&gt;
At FINN.no we were redesigning our control and view layers. The architectural team had decided on Spring-Web as a framework for the control layer due to its flexibility and for providing us a simpler migration path. For the front end we were a little unclear. In a department of ~60 developers we knew that the popular vote would lead us towards SiteMesh. And we knew why – for practical purposes sitemesh gives the front end developer more flexibility and definitely less xml editing.
But sitemesh has some serious shortcomings...&lt;/div&gt;

&lt;p&gt;&lt;span class=&quot;image-wrap&quot; style=&quot;float: right&quot;&gt;&lt;img style=&quot;border: 0px solid black&quot; src=&quot;/images/2012-07-25-the-ultimate-view-tiles-3/images.jpg&quot; alt=&quot;images&quot; width=&quot;145&quot; /&gt;&lt;/span&gt;&lt;/p&gt;

&lt;div style=&quot;font-size:90%;&quot;&gt;&lt;strong&gt;SiteMesh shortcomings:&lt;/strong&gt;&lt;ul&gt;&lt;li&gt;from a design perspective the Decorator pattern can undermine the seperation MVC intends,&lt;/li&gt;
	    &lt;li&gt;requires all possible html for a request in buffer requiring large amounts of memory&lt;/li&gt;
	    &lt;li&gt;unable to flush the response before the response is complete,&lt;/li&gt;
	    &lt;li&gt;requires more overall processing due to all the potentially included fragments,&lt;/li&gt;
	    &lt;li&gt;does not guaranteed thread safety, and&lt;/li&gt;
	    &lt;li&gt;does not provide any structure or organisation amongst jsps, making refactorings and other tricks awkward.&lt;/li&gt;
    &lt;/ul&gt;&lt;/div&gt;

&lt;p&gt;One of the alternatives we looked at was &lt;a class=&quot;external-link&quot; rel=&quot;nofollow&quot; href=&quot;http://tiles.apache.org/&quot;&gt;Apache Tiles.&lt;/a&gt; It follows the Composite Pattern, but within that allows one to take advantage of the Decorator pattern using a &lt;a class=&quot;external-link&quot; rel=&quot;nofollow&quot; href=&quot;http://tiles.apache.org/framework/tutorial/advanced/preparer.html&quot;&gt;ViewPreparer&lt;/a&gt;. This meant it provided by default what we considered a superior design but if necessary could also do what SiteMesh was good at. It already had integration with Spring, and the way it worked it meant that once the Spring-Web controller code was executed, the Spring’s view resolver would pass the model onto Tiles letting it do the rest. This gave us a clear MVC separation and an encapsulation ensuring single thread safety within the view domain.&lt;/p&gt;

&lt;blockquote&gt;“Tiles has been indeed the most undervalued project in past decade. It was the most useful part of struts, but when the focus shifted away from struts, tiles was forgotten. Since then struts as been outpaced by spring and JSF, however tiles is still the easiest and most elegant way to organize a complex web site, and it works not only with struts, but with every current MVC technology.” – Nicolas Le Bas&lt;/blockquote&gt;

&lt;p&gt;Yet the best Tiles was going to give wasn’t realised until we started experimenting a little more…&lt;/p&gt;

&lt;h2 id=&quot;step-0-spring-to-tiles-integration&quot;&gt;Step 0: Spring to Tiles Integration&lt;/h2&gt;

&lt;p&gt;The first step is integrating Tiles and Spring together. For Tiles-3 it boils down to registering a ViewResolver and a TilesConfigurer in your spring-web configuration.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-xml&quot; data-lang=&quot;xml&quot;&gt;&lt;span class=&quot;nt&quot;&gt;&amp;lt;bean&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;id=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;viewResolver&quot;&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;class=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;org.springframework.web.servlet.view.tiles3.TilesViewResolver&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;bean&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;id=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;tilesConfigurer&quot;&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;class=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;org.springframework.web.servlet.view.tiles3.TilesConfigurer&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;property&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;name=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;tilesInitializer&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;bean&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;class=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;no.finntech.control.servlet.tiles.FinnTilesInitialiser&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;/property&amp;gt;&lt;/span&gt;  
&lt;span class=&quot;nt&quot;&gt;&amp;lt;/bean&amp;gt;&lt;/span&gt;  &lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;div style=&quot;font-size:90%;&quot;&gt;
You need Spring-3.2 to get this to work, specifically &quot;spring-webmvc&quot;. If you're using an older version of Spring then you can &lt;a href=&quot;http://wever.org/spring-webmvc-tiles3-3.2.0.RC2-finn-1.jar&quot;&gt;download&lt;/a&gt; the required classes separately and add them to your classpath. (There's a &lt;a href=&quot;http://wever.org/spring-webmvc-tiles3-3.2.0.RC2-finn-1.pom&quot;&gt;pom&lt;/a&gt; file also available.)

The TilesConfigurer hooks in the &quot;tilesInitializer&quot; class. This is the way in Tiles-3 of providing the &lt;a class=&quot;external-link&quot; rel=&quot;nofollow&quot; href=&quot;https://tiles.apache.org/framework/config-reference.html&quot;&gt;configuration&lt;/a&gt;. If you don't specify one it'll use by default &lt;a href=&quot;http://tiles.apache.org/framework/apidocs/org/apache/tiles/extras/complete/CompleteAutoloadTilesInitializer.html&quot;&gt;CompleteAutoloadTilesInitializer&lt;/a&gt; which gives you a fully featured Tiles setup (you'll need tiles-extras.jar in the classpath). The setup here uses the FinnTilesInitialiser and FinnTilesContainerFactory classes to make possible further configurations.
For now FinnTilesContainerFactory is an empty class extending BasicTilesContainerFactory, and FinnTilesInitialiser looks like
&lt;/div&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-java&quot; data-lang=&quot;java&quot;&gt;&lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;FinnTilesInitialiser&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;extends&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;AbstractTilesInitializer&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;

    &lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;FinnTilesInitialiser&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{}&lt;/span&gt;

    &lt;span class=&quot;nd&quot;&gt;@Override&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;protected&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;AbstractTilesContainerFactory&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;createContainerFactory&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;TilesApplicationContext&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;context&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;FinnTilesContainerFactory&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;();&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;&lt;br /&gt;&lt;/p&gt;

&lt;h2 id=&quot;step-1-wildcards&quot;&gt;Step 1: Wildcards&lt;/h2&gt;
&lt;p&gt;&lt;img style=&quot;margin: 10px&quot; src=&quot;/images/2012-07-25-the-ultimate-view-tiles-3/Screenshot1.png&quot; border=&quot;0&quot; alt=&quot;create these files and folders&quot; align=&quot;right&quot; /&gt;&lt;/p&gt;

&lt;p&gt;&lt;br /&gt;&lt;/p&gt;

&lt;div style=&quot;font-size:90%;&quot;&gt;
We are going to create a basic website with three simple pages: cat, cow, and dog. The jsps making up these pages are shown to the right.

Composing these jsps together into their corresponding pages we encounter Tiles' most obvious downfall: every single jsp included must be declared in a definition in the tiles.xml file. Anyone that ever tried Tiles-1 knows this bad smell.

Since Tiles-2 this can be avoided by using &lt;a class=&quot;external-link&quot; rel=&quot;nofollow&quot; href=&quot;http://tiles.apache.org/framework/tutorial/advanced/wildcard.html&quot;&gt;wildcards&lt;/a&gt;¹, and when hearing people talk about tiles it is often clear this &lt;em&gt;dynamic composite&lt;/em&gt; paradigm hasn't yet superseded the old tiles prejudices.
&lt;br /&gt;

Let's declare a definition and template as below and create some files and folders as shown to the right:
&lt;/div&gt;

&lt;p&gt;&lt;br /&gt;&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-xml&quot; data-lang=&quot;xml&quot;&gt;//--tiles.xml
//--  anything that doesn't start with a slash is considered a definition here.
&lt;span class=&quot;nt&quot;&gt;&amp;lt;definition&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;name=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;REGEXP:([^/].*)&quot;&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;template=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;/WEB-INF/tiles/template.jsp&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;put-attribute&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;name=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;meta&quot;&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;value=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;/WEB-INF/tiles/{1}/meta.jsp&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;put-attribute&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;name=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;header&quot;&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;value=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;/WEB-INF/tiles/{1}/header.jsp&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;put-attribute&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;name=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;body&quot;&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;value=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;/WEB-INF/tiles/{1}/body.jsp&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;put-attribute&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;name=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;footer&quot;&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;value=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;/WEB-INF/tiles/{1}/footer.jsp&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;/definition&amp;gt;&lt;/span&gt;
//--template.jsp&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-xml&quot; data-lang=&quot;xml&quot;&gt;&lt;span class=&quot;cp&quot;&gt;&amp;lt;!DOCTYPE html&amp;gt;&lt;/span&gt;
&lt;span class=&quot;err&quot;&gt;&amp;lt;&lt;/span&gt;%@ prefix=&quot;tiles&quot; taglib uri=&quot;http://tiles.apache.org/tags-tiles&quot; %&amp;gt;
&lt;span class=&quot;nt&quot;&gt;&amp;lt;html&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;head&amp;gt;&lt;/span&gt;
        &lt;span class=&quot;nt&quot;&gt;&amp;lt;tiles:insertAttribute&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;name=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;meta&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;/head&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;body&amp;gt;&lt;/span&gt;
        &lt;span class=&quot;nt&quot;&gt;&amp;lt;div&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;id=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;header&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&amp;lt;tiles:insertAttribute&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;name=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;header&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;/&amp;gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
        &lt;span class=&quot;nt&quot;&gt;&amp;lt;div&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;id=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;body&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&amp;lt;tiles:insertAttribute&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;name=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;body&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;/&amp;gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
        &lt;span class=&quot;nt&quot;&gt;&amp;lt;div&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;id=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;footer&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&amp;lt;tiles:insertAttribute&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;name=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;footer&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;/&amp;gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;/body&amp;gt;&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;&amp;lt;/html&amp;gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;div style=&quot;font-size:90%;&quot;&gt;
Write a Spring controller to return definitions that match the names of the folders, that is the three definitions &quot;cat&quot;, &quot;dog&quot;, and &quot;cow&quot;.
&lt;/div&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-java&quot; data-lang=&quot;java&quot;&gt;&lt;span class=&quot;nd&quot;&gt;@Controller&lt;/span&gt;
&lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;MyWebsite&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;

    &lt;span class=&quot;nd&quot;&gt;@RequestMapping&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;/cat&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;ModelAndView&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;viewCat&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(){&lt;/span&gt;
        &lt;span class=&quot;nc&quot;&gt;Map&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;modelMap&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;HashMap&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;();&lt;/span&gt;
        &lt;span class=&quot;c1&quot;&gt;//...&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;ModelAndView&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;cat&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;modelMap&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;nd&quot;&gt;@RequestMapping&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;/dog&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;ModelAndView&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;viewDog&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(){&lt;/span&gt;
        &lt;span class=&quot;nc&quot;&gt;Map&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;modelMap&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;HashMap&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;();&lt;/span&gt;
        &lt;span class=&quot;c1&quot;&gt;//...&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;ModelAndView&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;dog&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;modelMap&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;nd&quot;&gt;@RequestMapping&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;/cow&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;ModelAndView&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;viewCow&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(){&lt;/span&gt;
        &lt;span class=&quot;nc&quot;&gt;Map&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;modelMap&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;HashMap&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;();&lt;/span&gt;
        &lt;span class=&quot;c1&quot;&gt;//...&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;ModelAndView&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;cow&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;modelMap&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;div style=&quot;font-size:90%;&quot;&gt;
&lt;strong&gt;Now with this setup we can keep adding new definitions and jsps without having to edit xml.&lt;/strong&gt;

&lt;br /&gt;
The Apache Tiles tutorial on wildcards explains that to extend the use of wildcards to also allow rich regular expressions one should use the &lt;code&gt;CompleteAutoloadTilesContainerFactory&lt;/code&gt;. In this article we are building up our own FinnTilesInitialiser and FinnTilesContainerFactory configuration classes. To enable both wilcards and regular expressions, distinguished by the use of prefixes, we need to override the following method in FinnTilesContainerFactory…
&lt;/div&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-java&quot; data-lang=&quot;java&quot;&gt;    &lt;span class=&quot;nd&quot;&gt;@Override&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;protected&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;PatternDefinitionResolver&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;createPatternDefinitionResolver&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;Class&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;cls&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;nc&quot;&gt;PrefixedPatternDefinitionResolver&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;r&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;PrefixedPatternDefinitionResolver&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;();&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;r&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;registerDefinitionPatternMatcherFactory&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;
                       &lt;span class=&quot;s&quot;&gt;&quot;WILDCARD&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;WildcardDefinitionPatternMatcherFactory&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;());&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;r&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;registerDefinitionPatternMatcherFactory&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;
                       &lt;span class=&quot;s&quot;&gt;&quot;REGEXP&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;RegexpDefinitionPatternMatcherFactory&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;());&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;r&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;div style=&quot;font-size:80%;&quot;&gt;&lt;img class=&quot;emoticon&quot; src=&quot;/images/2012-07-25-the-ultimate-view-tiles-3/information.gif&quot; alt=&quot;information&quot; width=&quot;16&quot; height=&quot;16&quot; align=&quot;absmiddle&quot; /&gt; &lt;strong&gt;Troubleshooting:&lt;/strong&gt; remember to include the &lt;a href=&quot;http://tiles.apache.org/framework/tiles-extras/project-summary.html&quot;&gt;&lt;i&gt;tiles-extras&lt;/i&gt;&lt;/a&gt; dependency! Without it you'll get &lt;code style=&quot;dont-size: 70%;&quot;&gt;...CompatibilityDigesterDefinitionsReader cannot be cast to ...Definition&lt;/code&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;/div&gt;

&lt;p&gt;&lt;br /&gt;&lt;/p&gt;

&lt;h2 id=&quot;step-2-the-fallback-pattern&quot;&gt;Step 2: The fallback pattern&lt;/h2&gt;
&lt;p&gt;&lt;img style=&quot;margin: 10px&quot; src=&quot;/images/2012-07-25-the-ultimate-view-tiles-3/Screenshot.png&quot; border=&quot;0&quot; alt=&quot;create these files and folders&quot; align=&quot;right&quot; /&gt;&lt;/p&gt;

&lt;p&gt;&lt;br /&gt;&lt;/p&gt;

&lt;div style=&quot;font-size:90%;&quot;&gt;
Not needing anymore to edit tiles.xml is nice, but as the number of definitions grows the number of duplicated jsps with also grow. For example maybe footers and headers are identical in nearly every definition.

Here we'll use the &lt;a class=&quot;external-link&quot; rel=&quot;nofollow&quot; href=&quot;http://tiles.apache.org/framework/apidocs/org/apache/tiles/extras/renderer/OptionsRenderer.html&quot;&gt;OptionsRenderer&lt;/a&gt; to provide a desired &lt;em&gt;fallback pattern&lt;/em&gt;.

In the xml a fallback is notated with the syntax &lt;code&gt;${options[myopts]}&lt;/code&gt; where &quot;myopts&quot; is a list-attribute that references the options in preferential order.

Introduce the &quot;common&quot; folder, as shown to the right, and the example turns into
&lt;/div&gt;

&lt;p&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-xml&quot; data-lang=&quot;xml&quot;&gt;//--tiles.xml
&lt;span class=&quot;nt&quot;&gt;&amp;lt;definition&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;name=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;REGEXP:([^/].*&quot;&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;template=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;/WEB-INF/tiles/template.jsp&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;put-attribute&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;name=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;meta&quot;&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;value=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;/WEB-INF/tiles/${options[myopts]}/meta.jsp&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;put-attribute&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;name=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;header&quot;&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;value=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;/WEB-INF/tiles/${options[myopts]}/header.jsp&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;put-attribute&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;name=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;body&quot;&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;value=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;/WEB-INF/tiles/${options[myopts]}/body.jsp&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;put-attribute&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;name=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;footer&quot;&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;value=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;/WEB-INF/tiles/${options[myopts]}/footer.jsp&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;put-list-attribute&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;name=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;myopts&quot;&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;cascade=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;true&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-attribute&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;value=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;{1}&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-attribute&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;value=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;common&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;/put-list-attribute&amp;gt;&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;&amp;lt;/definition&amp;gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;div style=&quot;font-size:90%;&quot;&gt;
To configure this &lt;code&gt;OptionsRenderer&lt;/code&gt; to work in your &lt;code&gt;TilesContainerFactory&lt;/code&gt; you'll also need to do the following
&lt;/div&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-java&quot; data-lang=&quot;java&quot;&gt;&lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;FinnTilesContainerFactory&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;extends&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;BasicTilesContainerFactory&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;...&lt;/span&gt;
    &lt;span class=&quot;nd&quot;&gt;@Override&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;protected&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Renderer&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;createTemplateAttributeRenderer&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;BasicRendererFactory&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;rendererFactory&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;ApplicationContext&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;applicationContext&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;TilesContainer&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;container&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;AttributeEvaluatorFactory&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;attributeEvaluatorFactory&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;

        &lt;span class=&quot;nc&quot;&gt;Renderer&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;original&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;super&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;createTemplateAttributeRenderer&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;rendererFactory&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;applicationContext&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;container&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;attributeEvaluatorFactory&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
        &lt;span class=&quot;nc&quot;&gt;OptionsRenderer&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;optionsRenderer&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;OptionsRenderer&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;applicationContext&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;original&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;optionsRenderer&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;....&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;div style=&quot;font-size:90%;&quot;&gt;
As the system developer writes new spring controllers to create completely new definitions the front end developer, with this new &lt;em&gt;Convention over Configuration&lt;/em&gt; setup, only needs to create the new folder and jsps for fragments they wish to customise. Again there's no further xml editing.
&lt;br /&gt;

&lt;strong&gt;No more xml editing and no duplicate JSPs.&lt;/strong&gt;&lt;br /&gt;
&lt;/div&gt;

&lt;blockquote style=&quot;font-size:80%;&quot;&gt;&lt;img class=&quot;emoticon&quot; src=&quot;/images/2012-07-25-the-ultimate-view-tiles-3/information.gif&quot; alt=&quot;information&quot; border=&quot;0&quot; width=&quot;16&quot; height=&quot;16&quot; align=&quot;absmiddle&quot; /&gt; For a large company this can help enforce UI standards by having control over the common folder — keeping an eye on UI overrides and customisations never become too outlandish with the standard look of the website. The front end developer can also be referencing this common folder for UI standards.&lt;/blockquote&gt;

&lt;p&gt;&lt;br /&gt;&lt;/p&gt;

&lt;h2 id=&quot;step-3-definition-includes&quot;&gt;Step 3: Definition includes&lt;/h2&gt;

&lt;div style=&quot;font-size:90%;&quot;&gt;
Realistically these fragments: meta, header, body, and footer; won't be enough. You can quickly be using scores of fragments for columns within the body, banners and advertisements, analytics, seo meta data, css and javascript meta includes, etc, etc.

With these different pages cat, dog, and cow it'll also be pretty certain that they'll have different &quot;actions&quot; applicable upon them. For example viewing, editing, and searching pages. Once again there'll be a lot of duplicate jsps.

By being able to dynamically inject one tiles definitions into another, referred to here as &quot;definition injection&quot;, this can all elegantly handed. First of all change the existing definition names into the form &quot;action.category&quot; making the controllers look like
&lt;/div&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-java&quot; data-lang=&quot;java&quot;&gt;    &lt;span class=&quot;nd&quot;&gt;@Controller&lt;/span&gt;
    &lt;span class=&quot;nd&quot;&gt;@RequestMapping&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;/cat&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;CatController&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;nd&quot;&gt;@RequestMapping&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;/cat/view&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;ModelAndView&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;viewCat&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(){&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;//...&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;ModelAndView&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;view.cat&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;modelMap&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
        &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
        &lt;span class=&quot;nd&quot;&gt;@RequestMapping&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;/cat/edit&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;ModelAndView&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;editCat&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(){&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;//...&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;ModelAndView&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;edit.cat&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;modelMap&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
        &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
        &lt;span class=&quot;nd&quot;&gt;@RequestMapping&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;/cat/search&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;ModelAndView&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;searchCat&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(){&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;//...&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;ModelAndView&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;search.cat&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;modelMap&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
        &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;nd&quot;&gt;@Controller&lt;/span&gt;
    &lt;span class=&quot;nd&quot;&gt;@RequestMapping&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;/dog&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;DogController&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;nd&quot;&gt;@RequestMapping&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;/dog/view&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;ModelAndView&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;viewDog&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(){/&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;/...&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;ModelAndView&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;view.dog&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;modelMap&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
        &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
        &lt;span class=&quot;nd&quot;&gt;@RequestMapping&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;/dog/edit&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;ModelAndView&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;editDog&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(){&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;//...&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;ModelAndView&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;edit.dog&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;modelMap&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
        &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
        &lt;span class=&quot;nd&quot;&gt;@RequestMapping&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;/dog/search&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;ModelAndView&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;searchDog&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(){&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;//...&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;ModelAndView&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;search.dog&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;modelMap&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
        &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;nd&quot;&gt;@Controller&lt;/span&gt;
    &lt;span class=&quot;nd&quot;&gt;@RequestMapping&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;/cow&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;CowController&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;nd&quot;&gt;@RequestMapping&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;/cow/view&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;ModelAndView&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;viewCow&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(){&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;//...&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;ModelAndView&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;view.cow&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;modelMap&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
        &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
        &lt;span class=&quot;nd&quot;&gt;@RequestMapping&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;/cow/edit&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;ModelAndView&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;editCow&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(){&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;//...&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;ModelAndView&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;edit.cow&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;modelMap&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
        &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
        &lt;span class=&quot;nd&quot;&gt;@RequestMapping&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;/cow/search&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;ModelAndView&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;searchCow&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(){&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;//...&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;ModelAndView&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;search.cow&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;modelMap&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
        &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;div style=&quot;font-size:90%;&quot;&gt;
then change the tiles.xml to introduce the definition includes
&lt;/div&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-xml&quot; data-lang=&quot;xml&quot;&gt;//-- tiles.xml
&lt;span class=&quot;nt&quot;&gt;&amp;lt;definition&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;name=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;REGEXP:([^/.][^.]*)\.([^.]+)&quot;&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;template=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;/WEB-INF/tiles/template.jsp&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;put-attribute&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;name=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;meta&quot;&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;value=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;/WEB-INF/tiles/${options[myopts]}/meta.jsp&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;put-attribute&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;name=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;header&quot;&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;value=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;/WEB-INF/tiles/${options[myopts]}/header.jsp&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;put-attribute&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;name=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;body&quot;&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;value=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;/WEB-INF/tiles/${options[myopts]}/body.jsp&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;put-attribute&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;name=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;footer&quot;&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;value=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;/WEB-INF/tiles/${options[myopts]}/footer.jsp&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;/&amp;gt;&lt;/span&gt;

    &lt;span class=&quot;c&quot;&gt;&amp;lt;!-- definition injection performed by DefinitionInjectingContainerFactory.instantiateDefinitionFactory(..) --&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;put-list-attribute&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;name=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;definition-injection&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-attribute&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;value=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;.category.{2}&quot;&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;type=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;string&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-attribute&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;value=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;.action.{1}.{2}&quot;&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;type=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;string&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;/put-list-attribute&amp;gt;&lt;/span&gt;

    &lt;span class=&quot;nt&quot;&gt;&amp;lt;put-list-attribute&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;name=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;myopts&quot;&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;cascade=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;true&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-attribute&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;value=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;{2}&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-attribute&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;value=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;{1}&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-attribute&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;value=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;common&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;/put-list-attribute&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;put-list-attribute&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;name=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;myopts-view&quot;&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;cascade=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;true&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-attribute&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;value=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;{2}&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-attribute&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;value=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;{1}&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-attribute&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;value=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;view&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;/put-list-attribute&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;put-list-attribute&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;name=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;myopts-edit&quot;&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;cascade=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;true&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-attribute&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;value=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;{2}&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-attribute&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;value=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;{1}&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-attribute&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;value=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;edit&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;/put-list-attribute&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;put-list-attribute&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;name=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;myopts-dog&quot;&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;cascade=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;true&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-attribute&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;value=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;{2}&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-attribute&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;value=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;{1}&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-attribute&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;value=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;dog&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;/put-list-attribute&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;put-list-attribute&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;name=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;myopts-cat&quot;&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;cascade=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;true&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-attribute&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;value=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;{2}&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-attribute&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;value=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;{1}&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-attribute&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;value=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;cat&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;/put-list-attribute&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;put-list-attribute&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;name=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;myopts-cow&quot;&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;cascade=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;true&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-attribute&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;value=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;{2}&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-attribute&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;value=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;{1}&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-attribute&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;value=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;cow&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;/put-list-attribute&amp;gt;&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;&amp;lt;/definition&amp;gt;&lt;/span&gt;

//-- tiles-core.xml
&lt;span class=&quot;nt&quot;&gt;&amp;lt;definition&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;name=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;REGEXP:\.action\.view\.([^.]+)&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;c&quot;&gt;&amp;lt;!-- override attributes --&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;put-attribute&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;name=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;body&quot;&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;value=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;/WEB-INF/tiles/${options[myopts-view]}/view_body.jsp&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;c&quot;&gt;&amp;lt;!-- search attributes --&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;put-attribute&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;name=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;view.content&quot;&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;value=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;/WEB-INF/tiles/${options[myopts-view]}/view_content.jsp&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;put-attribute&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;name=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;view.statistics&quot;&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;value=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;/WEB-INF/tiles/${options[myopts-view]}/view_statistics.jsp&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;/definition&amp;gt;&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;&amp;lt;definition&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;name=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;REGEXP:\.action\.edit\.([^.]+)&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;c&quot;&gt;&amp;lt;!-- override attributes --&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;put-attribute&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;name=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;body&quot;&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;value=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;/WEB-INF/tiles/${options[myopts-edit]}/edit_body.jsp&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;c&quot;&gt;&amp;lt;!-- edit attributes --&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;put-attribute&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;name=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;edit.form&quot;&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;value=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;/WEB-INF/tiles/${options[myopts-edit]}/edit_form.jsp&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;put-attribute&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;name=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;edit.status&quot;&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;value=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;/WEB-INF/tiles/${options[myopts-edit]}/edit_status.jsp&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;/definition&amp;gt;&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;&amp;lt;definition&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;name=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;REGEXP:\.action\.search\.([^.]+)&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;c&quot;&gt;&amp;lt;!-- override attributes --&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;put-attribute&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;name=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;body&quot;&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;value=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;/WEB-INF/tiles/${options[myopts-search]}/search_body.jsp&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;c&quot;&gt;&amp;lt;!-- search attributes --&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;put-attribute&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;name=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;search.form&quot;&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;value=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;/WEB-INF/tiles/${options[myopts-search]}/search_form.jsp&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;put-attribute&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;name=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;search.results&quot;&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;value=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;/WEB-INF/tiles/${options[myopts-search]}/search_results.jsp&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;/definition&amp;gt;&lt;/span&gt;
//-- tiles-cat.xml
&lt;span class=&quot;nt&quot;&gt;&amp;lt;definition&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;name=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;REGEXP:\.category\.cat&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;c&quot;&gt;&amp;lt;!-- cat attributes --&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;put-attribute&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;name=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;cat.extra_information&quot;&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;value=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;/WEB-INF/tiles/${options[myopts-cat]}/cat_extra_information.jsp&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;put-attribute&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;name=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;cat.feline_attributes&quot;&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;value=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;/WEB-INF/tiles/${options[myopts-cat]}/cat_feline_attributes.jsp&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;/definition&amp;gt;&lt;/span&gt;
//-- tiles-dog.xml
&lt;span class=&quot;nt&quot;&gt;&amp;lt;definition&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;name=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;REGEXP:\.category\.dog&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;c&quot;&gt;&amp;lt;!-- cat attributes --&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;put-attribute&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;name=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;dog.extra_information&quot;&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;value=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;/WEB-INF/tiles/${options[myopts-dog]}/dog_extra_information.jsp&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;put-attribute&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;name=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;dog.k9_attributes&quot;&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;value=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;/WEB-INF/tiles/${options[myopts-dog]}/dog_k9_attributes.jsp&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;/definition&amp;gt;&lt;/span&gt;
//-- tiles-cow.xml
&lt;span class=&quot;nt&quot;&gt;&amp;lt;definition&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;name=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;REGEXP:\.category\.cow&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;c&quot;&gt;&amp;lt;!-- cat attributes --&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;put-attribute&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;name=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;cow.extra_information&quot;&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;value=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;/WEB-INF/tiles/${options[myopts-cow]}/cow_extra_information.jsp&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;put-attribute&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;name=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;cow.farm&quot;&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;value=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;/WEB-INF/tiles/${options[myopts-cow]}/cow_farm.jsp&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;/definition&amp;gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;div style=&quot;font-size:90%;&quot;&gt;
There's a lot of configuration here now, but the idea isn't to avoid configuration altogether but to avoid having to edit the configuration everytime a new jsp or page is created. That is we are putting effort here into creating our &lt;em&gt;convention&lt;/em&gt; so that further configuration isn't required.

Now there's a clear separation between each category definition and each action definition. For example such separation helps various development roles work in parallel: front end developers can concentrate on categorical designs while system developers often work initially with actions jsps to get them functionally working before passing them off to the front end developers. Such &lt;em&gt;separation of concerns&lt;/em&gt; will show benefits in more ways that just this example.

The following DefinitionsFactory makes this all come together
&lt;/div&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-java&quot; data-lang=&quot;java&quot;&gt;&lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;FinnUnresolvingLocaleDefinitionsFactoryImpl&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;extends&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;UnresolvingLocaleDefinitionsFactory&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;

    &lt;span class=&quot;kd&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;static&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;DEF_INJECTION&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;definition-injection&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;FinnUnresolvingLocaleDefinitionsFactoryImpl&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{}&lt;/span&gt;

    &lt;span class=&quot;c1&quot;&gt;// this method may return null&lt;/span&gt;
    &lt;span class=&quot;nd&quot;&gt;@Override&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Definition&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;getDefinition&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;TilesRequestContext&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;tilesContext&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;nc&quot;&gt;Definition&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;c1&quot;&gt;// WEB-INF is a pretty clear indicator it is not a definition&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(!&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;startsWith&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;/WEB-INF/&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)){&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;super&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;getDefinition&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;tilesContext&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kc&quot;&gt;null&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;!=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;def&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;){&lt;/span&gt;
                &lt;span class=&quot;n&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Definition&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;def&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;// use a safe copy&lt;/span&gt;
                &lt;span class=&quot;nc&quot;&gt;Attribute&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;defList&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;def&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;getLocalAttribute&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;DEF_INJECTION&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;// explicit injected definitions&lt;/span&gt;
                &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kc&quot;&gt;null&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;!=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;defList&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;defList&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;instanceof&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;ListAttribute&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;){&lt;/span&gt;
                    &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;Attribute&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;inject&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;List&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;defList&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;getValue&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;()){&lt;/span&gt;
                        &lt;span class=&quot;n&quot;&gt;injectDefinition&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;((&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;inject&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;getValue&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(),&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;def&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;tilesContext&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
                    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
                &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
            &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
        &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;def&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;injectDefinition&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;fromName&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Definition&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;to&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;TilesRequestContext&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;cxt&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;assert&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;null&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;!=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;fromName&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;Definition not found &quot;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;fromName&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;nc&quot;&gt;Definition&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;getDefinition&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;fromName&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;cxt&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kc&quot;&gt;null&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;!=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;from&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kc&quot;&gt;null&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;!=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;from&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;getLocalAttributeNames&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;())&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
                &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;attrName&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;from&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;getLocalAttributeNames&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;())&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
                    &lt;span class=&quot;n&quot;&gt;to&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;putAttribute&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;attrName&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;from&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;getLocalAttribute&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;attrName&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;),&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
                &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
            &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
        &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;div style=&quot;font-size:90%;&quot;&gt;
To plug this DefinitionsFactory in make sure to return it from your TilesContainerFactory
&lt;/div&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-java&quot; data-lang=&quot;java&quot;&gt;&lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;FinnTilesContainerFactory&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;extends&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;BasicTilesContainerFactory&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;...&lt;/span&gt;
    &lt;span class=&quot;nd&quot;&gt;@Override&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;protected&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;UnresolvingLocaleDefinitionsFactory&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;instantiateDefinitionsFactory&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;TilesApplicationContext&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;applicationContext&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;TilesRequestContextFactory&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;contextFactory&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;LocaleResolver&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;resolver&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;FinnUnresolvingLocaleDefinitionsFactoryImpl&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;();&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;...&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;&lt;br /&gt;&lt;/p&gt;

&lt;h2 id=&quot;when-the-composite-pattern-is-superior&quot;&gt;When the Composite pattern is superior&lt;/h2&gt;

&lt;div style=&quot;font-size:90%;&quot;&gt;
It's nonsense to think that any one pattern is &lt;em&gt;better&lt;/em&gt;. Both the Composite pattern and the Decorator pattern have their strengths and weaknesses, even after we have progressed the Composite on to being highly dynamic and automated. They do, and achieve, quite different things and hence each should be used where applicable...
&lt;/div&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;

&lt;div style=&quot;font-size:90%;&quot;&gt;
&lt;img src=&quot;/images/2012-07-25-the-ultimate-view-tiles-3/lightbulb_on.gif&quot; border=&quot;0&quot; alt=&quot;lightbulb_on&quot; width=&quot;16&quot; height=&quot;16&quot; align=&quot;absmiddle&quot; /&gt; The &lt;a class=&quot;external-link&quot; rel=&quot;nofollow&quot; href=&quot;http://en.wikipedia.org/wiki/Decorator_pattern&quot;&gt;Decorator pattern&lt;/a&gt; allows front end code and design to be injected as we process. When a decision is known during the request processing the code can immediately build a decorator. The context here only holds all the built decorators. And at the end of the request lifecycle the page is assembled by putting together all these decorators. Decorators can also be built upon each other, or stacked, and this can be useful when the composition of the page is completely loose.
&lt;/div&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;

&lt;div style=&quot;font-size:90%;&quot;&gt;
&lt;img class=&quot;emoticon&quot; src=&quot;/images/2012-07-25-the-ultimate-view-tiles-3/lightbulb_on.gif&quot; alt=&quot;lightbulb_on&quot; border=&quot;0&quot; width=&quot;16&quot; height=&quot;16&quot; align=&quot;absmiddle&quot; /&gt; The &lt;a class=&quot;external-link&quot; rel=&quot;nofollow&quot; href=&quot;http://en.wikipedia.org/wiki/Composite_pattern&quot;&gt;Composite pattern&lt;/a&gt; presumes the page to be a &quot;composite&quot; made up from components, where each component is free to be itself a &quot;composite&quot;. The pattern allows more control of the composite's heirarchy: as the delegation is top-down as opposed to the Decorator pattern whom's is bottom-up. The composite pattern works well when the operations you need to perform on each object is limited, that is it isn't a problem that the operations you may perform on any one object is the lowest common demominator of operations that you can perform on every object.
&lt;/div&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;

&lt;div style=&quot;font-size:90%;&quot;&gt;
When you use jsp includes whether you like it or not you have a page heirarchy. If your jsps typically have a template with fragments you are already working within the composite pattern's paradigm: the template being the &quot;composite&quot; and the fragments being the components. Each fragment, each JSP, has a context that holds the model map, or variables, in various scopes, and within this context they are self-sufficient. The only operation required upon them by others is inclusion. Applying the composite pattern here means treating each fragment as a self-sufficient object, the only thing that can happen to it is it will be included.

This shows how we can design to reduce complexity and encourage there to be only one unified context.

It also shows how we can maintain a top-down control of the page's heirarchy, something necessary in a MVC design where the control layer wants to hand off a finished model map, ie one fixed and unified context, that the view layer is free to build itself off. In contrast when we choose the Decorator pattern we can run foul of letting code run in parallel to the MVC pattern.   &lt;br class=&quot;atl-forced-newline&quot; /&gt; &lt;br class=&quot;atl-forced-newline&quot; /&gt;
&lt;img style=&quot;border: 0px solid black;width:100%&quot; src=&quot;/images/2012-07-25-the-ultimate-view-tiles-3/patterns-flow.gif&quot; alt=&quot;Patterns flow&quot; align=&quot;center&quot; /&gt;
&lt;/div&gt;

&lt;p&gt;&lt;br /&gt;&lt;/p&gt;

&lt;h2 id=&quot;conclusion&quot;&gt;Conclusion&lt;/h2&gt;

&lt;div style=&quot;font-size:90%;&quot;&gt;
Front end developers, the system developers, and the architects need to work together. With the front end today often centered around loose coupling design: javascript's lack of type safety, ajax's separation from the server, and jQuery's liberation of the dom; it is all too easy to rebel against any form of structure or organisation for fear of being tied into the formalities of the strict-typed java world.  

Neither should the fear of giving up control of one's craftsmanship mean keeping logic and control in the View layer, this stuff belongs in the Control layer: and this means the system developers need to work harder to get the front end developers collaborating in the control layer, or the roles of system and front end developers need more overlap within teams. Everything has entropy, the larger the organisation, the larger the codebase, the less you fight the entropy, the quicker you end up with a pile of spaghetti in your lap.  

These four steps show you how Tiles-3 and the Composite pattern can be ramped up on steroids to give front end and systems developers what they want in a way that encourages them to work together.  
&lt;/div&gt;

&lt;p&gt;&lt;br /&gt;&lt;/p&gt;

&lt;div style=&quot;font-size:80%;&quot;&gt;
&lt;strong&gt;References&lt;/strong&gt;  &lt;br /&gt;
&amp;nbsp;&amp;nbsp;1. &lt;a href=&quot;http://tiles.apache.org&quot;&gt;Tiles-3&lt;/a&gt;  &lt;br /&gt;
&amp;nbsp;&amp;nbsp;2. &lt;a href=&quot;http://en.wikipedia.org/wiki/Composite_pattern&quot;&gt;Composite pattern&lt;/a&gt;  &lt;br /&gt;
&amp;nbsp;&amp;nbsp;3. &lt;a href=&quot;http://en.wikipedia.org/wiki/Decorator_pattern&quot;&gt;Decorator pattern&lt;/a&gt;  &lt;br /&gt;
&amp;nbsp;&amp;nbsp;4. &lt;a href=&quot;http://static.springsource.org/spring/docs/3.0.x/spring-framework-reference/html/spring-web.html&quot;&gt;Spring Web&lt;/a&gt;  &lt;br /&gt;
&amp;nbsp;&amp;nbsp;5. Integrating &lt;a href=&quot;http://static.springsource.org/spring/docs/3.0.x/spring-framework-reference/html/view.html#view-tiles&quot;&gt;Tiles and Spring&lt;/a&gt;  &lt;br /&gt;
&amp;nbsp;&amp;nbsp;6. Initial discussion regarding Tiles definition delegation[Gone]&lt;br /&gt;
&amp;nbsp;&amp;nbsp;7. Spring 3.2 with Tiles-3 :: &lt;a href=&quot;http://jira.springframework.org/browse/SPR-8825&quot;&gt;Tiles-3 support&lt;/a&gt;&lt;br /&gt;
&lt;/div&gt;
</content>
        
        <author>
            <name>mick</name>
            
            <email></email>
            
        </author>
        
    </entry>
    
    <entry>
        <title>Going all out with the Strap-on Project</title>
        <link rel="alternate" type="text/html" href="https://tech.finn.no2012/06/23/going-all-out-with-the-strap-on-project/"/>
        <updated>2012-06-23T18:32:28+00:00</updated>
        <id>https://tech.finn.no/2012/06/23/going-all-out-with-the-strap-on-project</id>
        <content type="html">&lt;p&gt;In January we started on an ambitious project called “the Strap-on project”. The infantile name aside this project is about rewriting our entire front-end tier using &lt;a href=&quot;http://oocss.org/&quot;&gt;Object Oriented CSS (OOCSS)&lt;/a&gt;, written by &lt;a href=&quot;http://www.stubbornella.org/&quot;&gt;Nicole Sulivan&lt;/a&gt;, as the secret sauce to achieving the desired results.&lt;/p&gt;

&lt;h2 id=&quot;why-oocss&quot;&gt;Why OOCSS?&lt;/h2&gt;

&lt;p&gt;At FINN we have teams grouped by business units and all of them have developers who contribute with code on our site. This is provided us with a head ache with CSS, because there are no clear idioms for how to best write CSS. In our case this had cause teams to create their own “islands” of CSS by using # trails, train wreck selector classes and !important-wars. Teams had duplicate CSS for the same layout and we had a hard time keeping the user experience consistent across different parts of our service.&lt;/p&gt;

&lt;p&gt;We had way too much CSS code in total and we sent loads of CSS on each page request with just a small percentage actually being used. This made page load slow and rendering slow. Page rendering speed is equal to money, so we had to change something.&lt;/p&gt;

&lt;h2 id=&quot;oocss---making-css-coding-a-thing-of-the-past&quot;&gt;OOCSS - making CSS coding a thing of the past&lt;/h2&gt;

&lt;p&gt;OOCSS is a framework on which to build your own. It provides with a base set of modules and concepts which is essentials to building stuff in HTML with CSS. Grids, modules, lines, etc. These base modules provides an abstraction on top of CSS which makes authoring of CSS a thing of the past. In order to layout basic pages all we do is to use the building blocks and put together what ever you want. Basic boring stuff such as clearing and the box-model is no longer anything you need to worry about, it’s taken care of. This enables developers to focus upon fulfilling business requirements instead of battling CSS differences in browser time and time again.&lt;/p&gt;

&lt;h2 id=&quot;why-not-less-compass-or-stuff-like-that&quot;&gt;Why not LESS, Compass or stuff like that?&lt;/h2&gt;

&lt;p&gt;The CSS language abstractions that exist out there are all very cool projects and make a whole lot of sense to use for a lot of projects. However, in our case choosing one of these tools right now would not provide us with some of the benefits we are looking for.&lt;/p&gt;

&lt;p&gt;At FINN we have a problem that we are duplicating CSS across our development teams. This makes creating a consistent user experience and making global changes harder than it should be. Using an abstraction would help us hide some these problems, but would not solve the underlying issue which is that we create too much code. OOCSS and its rigorous set of rules helps reduce the amount of code drastically and provides rigid rules which prevents duplication across teams. What we are looking for are:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Improve rendering speed&lt;/li&gt;
  &lt;li&gt;Improve development speed&lt;/li&gt;
  &lt;li&gt;Easier to provide a consistent user experience&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;a-touch-of-bootstrap-too&quot;&gt;A touch of Bootstrap too&lt;/h2&gt;

&lt;p&gt;The engineers over at Twitter has created an amazing framework, &lt;a href=&quot;http://twitter.github.com/bootstrap/&quot;&gt;Bootstrap&lt;/a&gt;, which is hugely popular all over the world. We have indeed paid close attention to how they have done and we are very much inspired by their work. However, going all out and just adopting Bootstrap was not an option. We feel it is too bloated and it does not provide the speed and performance benefits OOCSS gives.
Having said that, a lot of our setup with forms and form elements is influenced by how Bootstrap does things.&lt;/p&gt;

&lt;h2 id=&quot;half-way-how-does-it-look&quot;&gt;Half way, how does it look?&lt;/h2&gt;

&lt;p&gt;In short,iIt looks pretty darn impressive! We have removed more than twenty CSS files and reduced the amount of CSS code lines with more than thirty thousand (this does say quite a bit of the mess we where in, I know). We have migrated the motorized vehicles, jobs, real estate sections and the front page.&lt;/p&gt;

&lt;p&gt;Before
After&lt;/p&gt;

&lt;p&gt;Number of CSS files&lt;/p&gt;

&lt;p&gt;130&lt;/p&gt;

&lt;p&gt;38&lt;/p&gt;

&lt;p&gt;Lines of CSS code&lt;/p&gt;

&lt;p&gt;32 789&lt;/p&gt;

&lt;p&gt;2 927&lt;/p&gt;

&lt;p&gt;Lines of section specific CSS&lt;/p&gt;

&lt;p&gt;727&lt;/p&gt;

&lt;p&gt;89&lt;/p&gt;

&lt;p&gt;These are pretty impressive results and very much in line with what Nicole is talking about in her presentations about OOCSS and performance.&lt;/p&gt;

&lt;h2 id=&quot;is-it-all-singing-all-dancing&quot;&gt;is it all singing, all dancing?&lt;/h2&gt;

&lt;p&gt;Of course not! The responsive bit is something we are looking at reworking. Team Oppdrag has created one way of solving this and Team Reise another. For the Strap-on project we will probably end up with something in between. There are some things you need to take into consideration when choosing an approach:
How much control do you have over the markup being written? Or to put it in another way, how many people are working with the same code? The more people, the harder it is to apply very strict rules as people will be “tourists” in the code base and might have a hard time getting it.&lt;/p&gt;

&lt;p&gt;Another big challenge is to provide enough support and training to help everyone utilize the OOCSS framework so we can continue to reap benefits from what we have done, but that is probably some other blog post.&lt;/p&gt;
</content>
        
        <author>
            <name>espen</name>
            
            <email></email>
            
        </author>
        
    </entry>
    
    <entry>
        <title>Profiling and debugging view templates</title>
        <link rel="alternate" type="text/html" href="https://tech.finn.no2012/06/23/profiling-and-debugging-view-templates/"/>
        <updated>2012-06-23T10:31:21+00:00</updated>
        <id>https://tech.finn.no/2012/06/23/profiling-and-debugging-view-templates</id>
        <content type="html">&lt;p&gt;Ever needed to profile the tree of JSPs rendered server-side?
Most companies do and I’ve seen elaborate and rather convoluted ways to do so.&lt;/p&gt;

&lt;p&gt;With Tiles-3 you can use the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;PublisherRenderer&lt;/code&gt; to profile and debug not just the tree of JSPs but the full tree of all and any view templates rendered whether they be JSP, velocity, freemarker, or mustache.&lt;/p&gt;

&lt;p&gt;At FINN all web pages print such a tree at the bottom of the page. This helps us see what templates were involved in the rendering of that page, and which templates are slow to render.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/2012-06-23-profiling-and-debugging-view-templates/tiles-publisherrenderer-tree.png&quot; alt=&quot;Tiles publisherrenderer tree&quot; /&gt;]&lt;/p&gt;

&lt;p&gt;We also embed into the html source wrapping comments like&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;...template output...
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;the-code-please&quot;&gt;The code please&lt;/h2&gt;

&lt;p&gt;To do this register and then attach your own listener to the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;PublisherRenderer&lt;/code&gt;. For example in your &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;TilesContainerFactory&lt;/code&gt; (the class you extend to setup and configure Tiles) add to the methd createTemplateAttributeRenderer something like:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;    @Override
    protected Renderer createTemplateAttributeRenderer(BasicRendererFactory rendererFactory, ApplicationContext applicationContext, TilesContainer container, AttributeEvaluatorFactory attributeEvaluatorFactory) {

        Renderer renderer = super.createTemplateAttributeRenderer(rendererFactory, applicationContext, container, attributeEvaluatorFactory);
        PublisherRenderer publisherRenderer = new PublisherRenderer(renderer);
        publisherRenderer.addListener(new MyListener());
        return publisherRenderer;
    }
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Then implement your own listener, this implementation does just the wrapping comments with profiling information…&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;class MyListener implements PublisherRenderer.RendererListener {

    @Override
    public void start(String template, Request request) throws IOException {
        boolean first = null == request.getContext(&quot;request&quot;).get(&quot;started&quot;);
        if (!first) {
            // first check avoids writing before a template's doctype tag
            request.getPrintWriter().println(&quot;\n&quot;);
            startStopWatch(request);
        } else {
            request.getContext(&quot;request&quot;).put(&quot;started&quot;, Boolean.TRUE);
        }
    }

    @Override
    public void end(String template, Request request) throws IOException {
        Long time = stopStopWatch(request);
        if(null != time){
            request.getPrintWriter().println(&quot;\n&quot;);
        }
    }

    private void startStopWatch(Request request){
        Deque&amp;lt;stopwatch&amp;gt; stack = request.getContext(&quot;request&quot;).get(&quot;stack&quot;);
        if (null == stack) {
            stack = new ArrayDeque&amp;lt;stopwatch&amp;gt;();
            request.getContext(&quot;request&quot;).put(&quot;stack&quot;, stack);
        }
        StopWatch watch = new StopWatch();
        stack.push(watch);
        watch.start();
    }

    private Long stopStopWatch(Request request){
        Deque&amp;lt;stopwatch&amp;gt; stack = request.getContext(&quot;request&quot;).get(&quot;stack&quot;);
        return 0 &amp;lt; stack.size() ? stack.pop().getTime() : null;
    }
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;It’s quick to see the possibilities for simple and complex profiling open up here as well as being agnostic to the language of each particular template used. Learn more about &lt;a href=&quot;http://tiles.apache.org/&quot;&gt;Tiles-3 here&lt;/a&gt;.&lt;/p&gt;

</content>
        
        <author>
            <name>mick</name>
            
            <email></email>
            
        </author>
        
    </entry>
    
    <entry>
        <title>Technology Day 2012</title>
        <link rel="alternate" type="text/html" href="https://tech.finn.no2012/06/12/technology-day-2012/"/>
        <updated>2012-06-12T08:43:19+00:00</updated>
        <id>https://tech.finn.no/2012/06/12/technology-day-2012</id>
        <content type="html">&lt;p&gt;One of the ways of getting inspired and educated about technology here at FINN is through our “Tech-Dag” (which means something along the lines of Day of Technology). This year we have a packed program which consists of workshops and a bunch of lightning talks.&lt;/p&gt;

&lt;h2 id=&quot;workshops&quot;&gt;Workshops&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Clojure workshop&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Alternative languages on the Java Virtual Machine is a hot topic all over and at FINN we are exploring our options as well. Therefor we have invited &lt;a href=&quot;https://twitter.com/jhannes&quot;&gt;Johannes Brodwall&lt;/a&gt;, &lt;a href=&quot;http://twitter.com/ivarni&quot;&gt;Ivar Nilsen&lt;/a&gt; and &lt;a href=&quot;http://twitter.com/anderskar&quot;&gt;Anders Karlsen&lt;/a&gt; from Steria to do a introduction to Clojure workshop.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;FINN Way of Innovation&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Our Lean-navigators have been working hard to help our development teams make conscious decisions and to make sure they make the right priorities to maximize customer value. This workshop will touch upon some of the basic aspects and give the participants a way of giving input to the process.&lt;/p&gt;

&lt;h2 id=&quot;lightning-talks&quot;&gt;Lightning talks&lt;/h2&gt;

&lt;p&gt;Lightning talks are 10 minutes long and tightly timed. We hold them after lunch, starting at 1330 – 1500 pm. The topics are broad ranging, and not limited to programming at all.&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;
    &lt;p&gt;About love (… and ask yourself “why do we work 8 hours a day”)&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;Fragmentation of the Android and why it is not a problem for us&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;Testcomplete database code before check - is it possible?&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;Elastic Search&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;Geolocation with MongoDB&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;Traditional version control systems vs distributed version control systems (VCS -&amp;gt; DVCS).&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;Eat your own dogfood&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;Semantic web&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;Large IT projects should fail!&lt;/p&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;følg-oss-på-hash-tag-techdagen&quot;&gt;Følg oss på Hash tag #techdagen&lt;/h2&gt;

</content>
        
        <author>
            <name>espen</name>
            
            <email></email>
            
        </author>
        
    </entry>
    
    <entry>
        <title> When do you know, how much testing is enough?</title>
        <link rel="alternate" type="text/html" href="https://tech.finn.no2012/05/21/when-do-you-know-how-much-testing-is-enough/"/>
        <updated>2012-05-21T11:56:43+00:00</updated>
        <id>https://tech.finn.no/2012/05/21/when-do-you-know-how-much-testing-is-enough</id>
        <content type="html">&lt;p&gt;Let me begin with an example, from every days scenario at work. You are at the end of the sprint and have some errors reported. &lt;img src=&quot;data:image/png;base64,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&quot; alt=&quot;Img&quot; /&gt;You think that the quality of code and tests are good enough for release. Based on this the reported bugs the failures are minor and won’t have big impact after fixing them.  But the reality may be different, that you really are on opposite ends of the quality. The code and test quality is low, and the reported tests are few or no bugs. Your worst case scenario could be that deployment/release will go very bad and can be very expensive in a technical or business nature.&lt;/p&gt;

&lt;p&gt;It’s important to evaluate the possibility of finding more defects based on results you have gathered so far. The key is to analyze your rapport thoroughly because:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;
    &lt;p&gt;If you have just found a defect, this is a signal to keep testing. It may seem counter-intuitive but in general the more defects you find the more likely it is that there are additional defects.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;If you have only exercised a small portion of the overall functionality and have found defects, then this is a signal to continue testing.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;If you have been testing a particular piece of functionality for a while and are not finding any new defects, then this is a signal for you to stop testing. How much of the system’s functionality have you tested?&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;If there are significant features that are mostly or entirely not tested, then you will likely not be prepared to recommend it is good to go.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;False confidence is a very real danger. This is especially true for developers. I have seen or heard of many who have an unrealistically high level of confidence that their code works, in some cases without any testing whatsoever. As a specific example, I have heard developers state that if their code compiles, it is good enough to be promoted to acceptance testing.&lt;/p&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Testing by definition is comparing an expected result to an observed result. So it should be easy to answer the question how much testing is enough? But the answer could be based on number of factors such as:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;
    &lt;p&gt;Identifying risks early in your project on technical and business aspect.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;The project constraints such as time or budget. If a team decides to enlarge the scope of a project the time will become larger as well along with the cost. If the time constraint is tighter the scope may be reduced but the cost will remain high. If the team decides to tighten the budget, the scope will become smaller but the time will increase. The time constraint deals with the time necessary to finish a project. To successfully complete a project, the time constraint should be comprised of a schedule. You should have a specific schedule related to the time that it will take you to finish the project.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;Measure the complexity and risk of new features and how much of the product they impact.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;Measure bugs found during the release and bugs found after the release and in what areas of the product.&lt;/p&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;By prioritizing risks early it will help to determine how much testing is considered to be enough. Once you have considered the risk, you should find the goal of testing. Once you have obtained a sufficiently high level of confidence in different levels of test such as Unit testing, Integration testing, System testing, Acceptance testing and Regression testing you are done testing.&lt;/p&gt;

&lt;p&gt;It is possible to do enough testing but determining the how much is enough is difficult. Simply doing what is planned is not sufficient since it leaves the question as to how much should be planned? What is enough testing can only be confirmed by evaluating the results of testing? If lots of defects are found with a set of planned tests it is likely that more tests will be required to assure that the required level software quality is achieved. On the other hand if very few defects are found with the planned set of tests, then no more tests will be required.&lt;/p&gt;

&lt;p&gt;Testing should provide information to the stakeholders of the system, so that they can make an informed decision about whether to release a system into production or to customers. Testers are not responsible for making that decision, they are responsible for providing the information so that the decision can be made in the light of good information.&lt;/p&gt;

&lt;p&gt;So going back to my question, testing is done when its objectives have been achieved and more specifically, you are done testing when:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;
    &lt;p&gt;You are unlikely to find additional defects.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;You have a sufficiently high level of confidence that the software is ready to be released.&lt;/p&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Vivek Sharma
Test Manager at FINN.no&lt;/p&gt;
</content>
        
        <author>
            <name>vivek</name>
            
            <email></email>
            
        </author>
        
    </entry>
    
    <entry>
        <title>Putting a mustache on Tiles-3</title>
        <link rel="alternate" type="text/html" href="https://tech.finn.no2012/05/15/putting-a-mustache-on-tiles-3/"/>
        <updated>2012-05-15T18:58:12+00:00</updated>
        <id>https://tech.finn.no/2012/05/15/putting-a-mustache-on-tiles-3</id>
        <content type="html">&lt;p&gt;We’re proud to see a contribution from one of our developers end up in the Tiles-3 release!&lt;/p&gt;

&lt;p&gt;The front-end architecture of FINN.no is evolving to be a lot more advanced and a lot more work is being done by client-side scripts. In order to maintain first time rendering speeds and to prevent duplicating template-code we needed something which allowed us to reuse templates both client- and server-side. This is where &lt;a href=&quot;http://mustache.github.com/&quot;&gt;mustache templates&lt;/a&gt; have come into play. We could’ve gone ahead and done a large template framework review, like others have done, but we instead opted to just solve the problem with the technology we already had.&lt;/p&gt;

&lt;p&gt;Morten Lied Johansen’s &lt;a href=&quot;http://tiles.apache.org/tiles-request/xref/org/apache/tiles/request/mustache/MustacheRenderer.html&quot;&gt;contribution&lt;/a&gt; allows Tiles-3 to render mustache templates. Existing jsp templates can be rewritten into mustache without having to touch surrounding templates or code!&lt;/p&gt;

&lt;h2 id=&quot;the-code-please&quot;&gt;The code please&lt;/h2&gt;

&lt;p&gt;To get Tiles-3 to do this include the tiles-request-mustache library and configure your TilesContainerFactory like&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;    protected void registerAttributeRenderers(...) {
        MustacheRenderer mustacheRenderer = new MustacheRenderer();
        mustacheRenderer.setAcceptPattern(Pattern.compile(&quot;.*.mustache&quot;));
        rendererFactory.registerRenderer(&quot;mustache&quot;, mustacheRenderer);
        ...
    }
    protected Renderer createTemplateAttributeRenderer(...) {
        final ChainedDelegateRenderer chainedRenderer = new ChainedDelegateRenderer();
        chainedRenderer.addAttributeRenderer(rendererFactory.getRenderer(&quot;mustache&quot;));
        ...
    }
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;then you’re free to replace existing tiles attributes like&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&amp;lt;put-attribute name=&quot;my_template&quot; value=&quot;/WEB-INF/my_template.jsp&quot;&amp;gt;&amp;lt;/put-attribute&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;with stuff like&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&amp;lt;put-attribute name=&quot;my_template&quot; value=&quot;/my_template.mustache&quot;&amp;gt;&amp;lt;/put-attribute&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Good stuff FINN!&lt;/p&gt;
</content>
        
        <author>
            <name>mick</name>
            
            <email></email>
            
        </author>
        
    </entry>
    
    <entry>
        <title>QA role is dead but development and testing together provides quality for FINN.no</title>
        <link rel="alternate" type="text/html" href="https://tech.finn.no2012/05/07/qa-role-is-dead-but-development-and-testing-together-provides-quality-for-finn/"/>
        <updated>2012-05-07T07:29:20+00:00</updated>
        <id>https://tech.finn.no/2012/05/07/qa-role-is-dead-but-development-and-testing-together-provides-quality-for-finn</id>
        <content type="html">&lt;p&gt;When a developer is finished with a development issue, it must be tested. People often think the goal of testing should be to find all the errors. Well, it’s not entirely realistic. Actually, it’s impossible! Despite the title of “quality insurance” testers can never really ensure level of quality, nor can they be expected to find all the errors. The real goal of testing should be to improve the software. As at the end of the day we want to minimize the cost of fixing critical bugs after a release, minimize cost of handling customer complaints and pushing a patch in the production. So it is import to understand that Quality Assurance is a process, not a department.&lt;/p&gt;

&lt;p&gt;Today there are basically two different components of QA role in FINN. One of which is “QA in a team” and the other one is of a “team without the QA”. The QA role today is often a safety net for many developers and teams. The role involves manual testing in the sprint, regression testing and writing Cucumber tests. In addition this role has some administrative tasks such as being contact person for the Release Manager, participating in the release status meeting, filling out the checklist of early / late shift, contact for Tech Support (E-journal issues), participating in status meetings at Call Center after release, and verifying the patch to production. Everything that’s listed so far is managed also by teams without QA. What we have seen so far, is that a team without QA delivers the same quality as the team with QA in developing an FINN application.&lt;/p&gt;

&lt;p&gt;Then comes the question, how do they manage to do it?&lt;/p&gt;

&lt;p&gt;They have the same responsibilities as the “QA in a team”, but they have different practices on how they manage the tasks (some have rolling QA role, others have a fixed arrangement but they are NOT dependent on one individual person).&lt;/p&gt;

&lt;p&gt;If we want to deliver a good quality code, testing must become a central pillar of the development process. When I say the role of QA is dead, but development and testing together provides quality for FINN, by that I mean  &lt;img src=&quot;data:image/png;base64,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&quot; alt=&quot;&quot; /&gt;that the whole team should focus on unit testing, integration testing, system testing and functional testing (as part of the pipeline). This will build and provide quality from top to bottom in the product stack.&lt;/p&gt;

&lt;p&gt;Despite back-end test automation, front end problems with web applications are always discovered by human testers, including issues related to browser compatibility or CSS flaws. This can be covered by focusing on exploratory testing, which is also done in a team with no QA.&lt;/p&gt;

&lt;p&gt;The QA testers should rather concentrate on the planning of tests within their teams. They should focus on three things.&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;
    &lt;p&gt;Attributes: Describes the high level concept testing are meant to ensure performance testing, usable testing, secure testing, accessible testing and so forth.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;Components: Define the major code chunks that comprise the product. These are classes, module names and features of the application.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;Capabilities: Describes user actions and activities.&lt;/p&gt;
  &lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;We know it very well that we cannot test everything so there is no pointing in investing too much time in documenting everything either? And we know as we start testing things the schedule, the requirements, the architecture etc are going to change. But the greater benefit is that whole team takes the responsibility for the quality, quicker feedback, tests documenting the code and not the least satisfied customers.&lt;/p&gt;

&lt;p&gt;Written by Vivek Sharma
Test Manager at FINN.no&lt;/p&gt;

</content>
        
        <author>
            <name>vivek</name>
            
            <email></email>
            
        </author>
        
    </entry>
    
    <entry>
        <title>Spreading love and good vibrations on GitHub</title>
        <link rel="alternate" type="text/html" href="https://tech.finn.no2011/11/21/spreading-love-and-good-vibrations-on-github/"/>
        <updated>2011-11-21T19:02:40+00:00</updated>
        <id>https://tech.finn.no/2011/11/21/spreading-love-and-good-vibrations-on-github</id>
        <content type="html">&lt;p&gt;We have finally made an effort to put some of our repos out on GitHub under the &lt;a href=&quot;https://github.com/organizations/finn-no&quot;&gt;finn-no organization&lt;/a&gt;. It is not exactly filled with stuff right now, but we aim to put a lot more of our libraries and tools out here.&lt;/p&gt;

&lt;p&gt;Currently there are a few repositories out there:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;a href=&quot;https://github.com/finn-no/eventhub&quot;&gt;eventHub&lt;/a&gt;&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;Awesome Board[Gone]&lt;/p&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;eventhub&quot;&gt;eventHub&lt;/h2&gt;

&lt;p&gt;This is just a tiny piece of code which enables you to build loosely coupled components or applications by utilizing the &lt;a href=&quot;http://en.wikipedia.org/wiki/Publish%E2%80%93subscribe_pattern&quot;&gt;publish-subscribe pattern&lt;/a&gt;. The &lt;a href=&quot;https://github.com/finn-no/eventhub&quot;&gt;eventHub&lt;/a&gt; is just an object which publishes event to those to registered event listeners functions.&lt;/p&gt;

&lt;p&gt;We have written one-page JS applications using only the hub besides jQuery for DOM manipulation. It scales pretty well and it is pretty much what you’d need to create an application. Once your application grows to be quite large you might want to add some other architectural components, but for small to medium sized projects it is brilliant. It is used for the dashboard part for the Awesome Board[Gone].&lt;/p&gt;

&lt;h2 id=&quot;awesome-board&quot;&gt;Awesome Board&lt;/h2&gt;

&lt;p&gt;The Awesome Board[Gone] is the application which has featured in two articles about visualizing quality. We have shown it a few times to different audiences and due to request for making it available we decided it was a good thing to share it on GitHub. There are some pieces missing from the version we run internally, but have patience the rest might still make it to a repo near you.&lt;/p&gt;

&lt;p&gt;Enjoy! Feel free to drop us a line in the comments section or &lt;a href=&quot;http://twitter.com/FINN_tech&quot;&gt;follow us on Twitter&lt;/a&gt;.&lt;/p&gt;
</content>
        
        <author>
            <name>espen</name>
            
            <email></email>
            
        </author>
        
    </entry>
    
    <entry>
        <title>We rocked out at the Smidig conference!</title>
        <link rel="alternate" type="text/html" href="https://tech.finn.no2011/11/19/we-rocked-out-at-the-smidig-conference/"/>
        <updated>2011-11-19T12:31:11+00:00</updated>
        <id>https://tech.finn.no/2011/11/19/we-rocked-out-at-the-smidig-conference</id>
        <content type="html">&lt;p&gt;The annual &lt;a href=&quot;http://smidig.no/&quot;&gt;Smidig conference&lt;/a&gt; in Oslo, Norway was held last week and as usual people from FINN.no made a big contribution to the conference. We hade three lightening talks during the two day conference and there were about ten attendants in total. This community event brings together newbies and veterans of agile software development for two days of lightning talks and open space sessions. We all had a great time at the conference and are very much looking forward to next years conference.&lt;/p&gt;

&lt;p&gt;Our three lightning talks spanned from a Norwegian version of over to a talk showing how we transformed our customer service team and the last one about how you need to measure in order to achieve the full effect of agile processes. You can check out the videos below, but be warned they are all in Norwegian.&lt;/p&gt;

&lt;h2 id=&quot;transforming-our-customer-services-team&quot;&gt;Transforming our customer services team&lt;/h2&gt;

&lt;p&gt;Jo Anders Heir held a talk about how we have worked to transform our customer service team from being a shield preventing customer feedback from reaching product organization into a megafon which blasts information throughout the organization. It shows how simple software and processes can help you make sure you work on what really matters for you users and customers.&lt;/p&gt;

&lt;h2 id=&quot;measure-kpis-iterate-and-release-the-three-things-you-need-to-do-to-succeseed-with-agile&quot;&gt;Measure KPI’s, iterate and release. The three things you need to do to succeseed with agile&lt;/h2&gt;

&lt;p&gt;Arve Søreide talked about three things you need to do in order to achieve the desired effects from using agile methods. You need to mease Key Performance Indicators while you iterate and release frequently. Only then can you harvest the big benefits from using agile methods.&lt;/p&gt;

&lt;h2 id=&quot;an-end-to-negativity---norwegian-style&quot;&gt;An end to negativity - Norwegian style&lt;/h2&gt;

&lt;p&gt;This talk was originally held by &lt;a href=&quot;https://twitter.com/voodootikigod&quot;&gt;Chris William&lt;/a&gt; at &lt;a href=&quot;http://www.jsconf.eu&quot;&gt;JSConf EU&lt;/a&gt; 2011 and you should check out &lt;a href=&quot;https://www.youtube.com/watch?v=17rkSdkc5TI&quot;&gt;the original version of An End To Negativity&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;A big thanks from all of us at FINN.no to the crew who’s hard work made this years conference a huge success. See you all again next year!&lt;/p&gt;
</content>
        
        <author>
            <name>espen</name>
            
            <email></email>
            
        </author>
        
    </entry>
    
    <entry>
        <title>Make real estate brokers happy with hard boiled business cards</title>
        <link rel="alternate" type="text/html" href="https://tech.finn.no2011/10/14/make-real-estate-brokers-happy-with-hard-boiled-business-cards/"/>
        <updated>2011-10-14T08:21:02+00:00</updated>
        <id>https://tech.finn.no/2011/10/14/make-real-estate-brokers-happy-with-hard-boiled-business-cards</id>
        <content type="html">&lt;p&gt;FINN.no has recently released new pages for real estate ads. Larger images, broker business cards and tabs for additional information are some of the new features on this page. A long process of in-depth users interviews, workshops with real estate brokers, user testing of design sketches and several sprints of development, we are proud to finally see the new pages on &lt;a href=&quot;http://www.finn.no/&quot;&gt;www.finn.no&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;http://www.finn.no/finn/realestate/homes/result??sort=3&quot;&gt;Pick a nice home from this list to view the ads&lt;/a&gt;
Cut the general crap - show me the business cards!&lt;/p&gt;

&lt;h2 id=&quot;whats-in-it-for-the-user&quot;&gt;What’s in it for the user?&lt;/h2&gt;

&lt;p&gt;&lt;img src=&quot;/images/2011-10-14-make-real-estate-brokers-happy-with-hard-boiled-business-cards/full_ad.jpg&quot; alt=&quot;Full ad&quot; /&gt;
Larger images and the possibility to navigate through images without leaving the page were the first thing that was noticed, and liked, during our user testing sessions. For a full size view of 900 pixel images we have still kept a link to our old image viewing page. However, after release we have got feedback from many users who failed to find this link and complain that images are smaller. There is still work to be done to improve this.&lt;/p&gt;

&lt;p&gt;The information about the home is divided into primary and secondary information. The prime information, like price, area, address and viewings, is put at the top of the page, so the user won’t need to scroll to find it. More detailed information, like facilities, plot size and descriptions is located below the prime information and the image viewing module.&lt;/p&gt;

&lt;p&gt;Secondary information is integrated by tabs. Fakta (facts) is the default tab, containing information about the estate from the broker. Prisstatistikk (price statistics) is highlights from Eiendomspulsen, which displays information like average price per square meter in the area and prices for previous sales of the actual estate. Neighborhood profile is an additional service brokers may add to their prospects with information about nearby services, schools and demographics.&lt;/p&gt;

&lt;h2 id=&quot;whats-in-it-for-the-broker&quot;&gt;What’s in it for the broker?&lt;/h2&gt;

&lt;p&gt;Realestate brokers have requested more options for building brand awareness in the prospect. To meet this request our designers suggested the brokers may design their own business cards for their contact information and to use their profile colors at some other elements on the page. They may also offer their services after the last image in the image gallery and in a tab next to Price statistics and Neighborhood profile. Interviews with users indicate that information from the broker won’t be regarded as ordinary banner ads as long as they provide information that is relevant for users who are in the process of changing their home. Krogsveen (a major realestate agent in Norway) has taken good advantage of this banner position by listing properties they have recently sold in the same area.&lt;/p&gt;

&lt;h2 id=&quot;how-to-make-the-business-cards&quot;&gt;How to make the business cards&lt;/h2&gt;

&lt;p&gt;There are three ways to display the broker’s contact information. Basic prospects have a simple listing of name, position, phone and fax numbers along with a company logo. Brokers who are using expanded prospects, which are prospects with the broker’s own set of banners, have their contact information styled as a business card. Brokers may customize these cards with their own css, background image and a picture of the broker.&lt;/p&gt;

&lt;p&gt;Some prospects have two brokers listed, but to save space only one broker is visible at the time. In expanded prospects business cards will swap when the user click a looping arrow or the card in the back. In basic prospects the name of the other broker slide in when clicking the “more contact persons” link. Jquery animation is used to swap the cards and CSS transitions is used for the sliding of contacts in basic prospects.&lt;/p&gt;

&lt;p&gt;The markup is the same for all levels of contact information, supporting the hcard microformat, while there are three layers of css. Styling of the contact information on basic prospects is in the main css file. Default business cards are styled by using a seperate css file which adds the business card look and overrides the default css where necessary. Customized business cards are styled by a third layer of css, which overrides the classes from the default card to add a background image and their company’s colors and fonts.&lt;/p&gt;

&lt;p&gt;By the time of release we had 13 different customized business cards on our prospects.&lt;/p&gt;

&lt;h3 id=&quot;contact-information-for-a-basic-prospect&quot;&gt;Contact information for a basic prospect&lt;/h3&gt;

&lt;p&gt;&lt;img src=&quot;/images/2011-10-14-make-real-estate-brokers-happy-with-hard-boiled-business-cards/basic_contact.png&quot; alt=&quot;Contact&quot; /&gt;&lt;/p&gt;

&lt;p&gt;HTML code for a business card looks like this:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-html&quot; data-lang=&quot;html&quot;&gt;    &lt;span class=&quot;nt&quot;&gt;&amp;lt;address&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;id=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;flip_0&quot;&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;class=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;vcard flip  backgroundimage&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;a&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;href=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;(url)&quot;&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;class=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;orglogo&quot;&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;target=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;_blank&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&amp;lt;img&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;src=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;(url)&quot;&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;alt=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Utleiemegleren Sinsen&quot;&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;border=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;0&quot;&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;class=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;logo&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&amp;lt;/a&amp;gt;&lt;/span&gt;
      &lt;span class=&quot;nt&quot;&gt;&amp;lt;span&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;class=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;fn n brokerName&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;Karianne Johannessen&lt;span class=&quot;nt&quot;&gt;&amp;lt;/span&amp;gt;&lt;/span&gt;
      &lt;span class=&quot;nt&quot;&gt;&amp;lt;span&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;class=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;ul&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;span&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;class=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;li title brokerTitle&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;Supermegler&lt;span class=&quot;nt&quot;&gt;&amp;lt;/span&amp;gt;&lt;/span&gt;
        &lt;span class=&quot;nt&quot;&gt;&amp;lt;span&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;class=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;li brokerPhoneHolder&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;dl&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;class=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;tel brokerPhone&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;dt&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;class=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;type hideText&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;Work&lt;span class=&quot;nt&quot;&gt;&amp;lt;/dt&amp;gt;&lt;/span&gt;
            &lt;span class=&quot;nt&quot;&gt;&amp;lt;dt&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;class=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;displayText&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;Telefon&lt;span class=&quot;nt&quot;&gt;&amp;lt;/dt&amp;gt;&lt;/span&gt;
            &lt;span class=&quot;nt&quot;&gt;&amp;lt;dd&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;class=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;value&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;22 79 66 26&lt;span class=&quot;nt&quot;&gt;&amp;lt;/dd&amp;gt;&lt;/span&gt;
          &lt;span class=&quot;nt&quot;&gt;&amp;lt;/dl&amp;gt;&lt;/span&gt;
        &lt;span class=&quot;nt&quot;&gt;&amp;lt;/span&amp;gt;&lt;/span&gt;
        &lt;span class=&quot;nt&quot;&gt;&amp;lt;span&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;class=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;li brokerMobilHolder&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;dl&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;class=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;tel brokerMobil&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;dt&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;class=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;type hideText&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;Cell&lt;span class=&quot;nt&quot;&gt;&amp;lt;/dt&amp;gt;&lt;/span&gt;
            &lt;span class=&quot;nt&quot;&gt;&amp;lt;dt&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;class=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;displayText&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;Mobil&lt;span class=&quot;nt&quot;&gt;&amp;lt;/dt&amp;gt;&lt;/span&gt;
            &lt;span class=&quot;nt&quot;&gt;&amp;lt;dd&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;class=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;value&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;48133956&lt;span class=&quot;nt&quot;&gt;&amp;lt;/dd&amp;gt;&lt;/span&gt;
          &lt;span class=&quot;nt&quot;&gt;&amp;lt;/dl&amp;gt;&lt;/span&gt;
        &lt;span class=&quot;nt&quot;&gt;&amp;lt;/span&amp;gt;&lt;/span&gt;
        &lt;span class=&quot;nt&quot;&gt;&amp;lt;span&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;class=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;li brokerFaxHolder&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;dl&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;class=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;tel brokerFax&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;dt&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;class=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;type hideText&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;Fax&lt;span class=&quot;nt&quot;&gt;&amp;lt;/dt&amp;gt;&lt;/span&gt;
            &lt;span class=&quot;nt&quot;&gt;&amp;lt;dt&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;class=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;type&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;Faks&lt;span class=&quot;nt&quot;&gt;&amp;lt;/dt&amp;gt;&lt;/span&gt;
            &lt;span class=&quot;nt&quot;&gt;&amp;lt;dd&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;class=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;value&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;22796601&lt;span class=&quot;nt&quot;&gt;&amp;lt;/dd&amp;gt;&lt;/span&gt;
          &lt;span class=&quot;nt&quot;&gt;&amp;lt;/dl&amp;gt;&lt;/span&gt;
        &lt;span class=&quot;nt&quot;&gt;&amp;lt;/span&amp;gt;&lt;/span&gt;
        &lt;span class=&quot;nt&quot;&gt;&amp;lt;span&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;class=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;li brokerEmail&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;a&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;data-click-mt=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;mailto&quot;&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;href=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;(url)&quot;&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;class=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;click-track&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;Send epost&lt;span class=&quot;nt&quot;&gt;&amp;lt;/a&amp;gt;&lt;/span&gt;
        &lt;span class=&quot;nt&quot;&gt;&amp;lt;/span&amp;gt;&lt;/span&gt;
      &lt;span class=&quot;nt&quot;&gt;&amp;lt;/span&amp;gt;&lt;/span&gt;
      &lt;span class=&quot;nt&quot;&gt;&amp;lt;div&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;class=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;clearall&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;/address&amp;gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;This is the basic css for contact information:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-css&quot; data-lang=&quot;css&quot;&gt;    &lt;span class=&quot;nc&quot;&gt;.contact&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;overflow&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;hidden&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;nl&quot;&gt;width&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;330px&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;}&lt;/span&gt;
    &lt;span class=&quot;nc&quot;&gt;.contact&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;.cardWrapper&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;position&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;relative&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;nl&quot;&gt;width&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;700px&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;}&lt;/span&gt;
    &lt;span class=&quot;nc&quot;&gt;.contact&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;address&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;-moz-transition&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;left&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;0.7s&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ease-in-out&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;-webkit-transition&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;left&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;0.7s&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ease-in-out&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;nl&quot;&gt;-o-transition&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;left&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;0.7s&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ease-in-out&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;nl&quot;&gt;-ms-transition&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;left&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;0.7s&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ease-in-out&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;nl&quot;&gt;transition&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;left&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;0.7s&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ease-in-out&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;err&quot;&gt;   &lt;/span&gt; &lt;span class=&quot;nl&quot;&gt;background-color&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;m&quot;&gt;#FFFFFF&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;nl&quot;&gt;display&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;block&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;nl&quot;&gt;float&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;left&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;nl&quot;&gt;font-style&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;normal&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;nl&quot;&gt;position&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;relative&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;nl&quot;&gt;width&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;m&quot;&gt;330px&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;}&lt;/span&gt;
    &lt;span class=&quot;nc&quot;&gt;.contact&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;.hideText&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;display&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;none&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;}&lt;/span&gt;
    &lt;span class=&quot;nc&quot;&gt;.contact&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;.orglogo&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;float&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;right&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;}&lt;/span&gt;
    &lt;span class=&quot;nc&quot;&gt;.contact&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;.brokerName&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;display&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;block&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;nl&quot;&gt;font-size&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;m&quot;&gt;12px&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;nl&quot;&gt;font-weight&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;normal&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;nl&quot;&gt;margin&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;m&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;}&lt;/span&gt;
    &lt;span class=&quot;nc&quot;&gt;.contact&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;.li&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;clear&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;left&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;nl&quot;&gt;display&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;block&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;nl&quot;&gt;margin&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;nl&quot;&gt;padding&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;}&lt;/span&gt;
    &lt;span class=&quot;nc&quot;&gt;.contact&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;dt&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;float&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;left&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;nl&quot;&gt;padding-right&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;0.5em&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;}&lt;/span&gt;
    &lt;span class=&quot;nc&quot;&gt;.contact&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;dd&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;float&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;left&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;}&lt;/span&gt;
    &lt;span class=&quot;nc&quot;&gt;.contact&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;.moreContacts&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;float&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;left&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;nl&quot;&gt;width&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;m&quot;&gt;330px&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;nl&quot;&gt;text-align&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;right&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;nl&quot;&gt;margin-bottom&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;m&quot;&gt;20px&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;nc&quot;&gt;.contact&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;address&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;left&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;m&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;}&lt;/span&gt;
    &lt;span class=&quot;nc&quot;&gt;.contact&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;address&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;.backCard&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;position&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;relative&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;nl&quot;&gt;left&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;m&quot;&gt;360px&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;}&lt;/span&gt;
    &lt;span class=&quot;nc&quot;&gt;.contact&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;address&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt;&lt;span class=&quot;err&quot;&gt; &lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;address&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;left&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;m&quot;&gt;-330px&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;}&lt;/span&gt;
    &lt;span class=&quot;nc&quot;&gt;.contact&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;address&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;address&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;.backCard&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;left&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;m&quot;&gt;30px&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;h3 id=&quot;default-styling-of-a-business-card&quot;&gt;Default styling of a business card&lt;/h3&gt;

&lt;p&gt;&lt;img src=&quot;/images/2011-10-14-make-real-estate-brokers-happy-with-hard-boiled-business-cards/card_basic.png&quot; alt=&quot;Card&quot; /&gt;&lt;/p&gt;

&lt;p&gt;This is the second layer of css which forms a basic business card:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-css&quot; data-lang=&quot;css&quot;&gt;    &lt;span class=&quot;nf&quot;&gt;#flip_0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;z-index&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;m&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;nl&quot;&gt;top&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;m&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;nl&quot;&gt;left&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;m&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;}&lt;/span&gt;
    &lt;span class=&quot;nf&quot;&gt;#flip_1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;z-index&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;m&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;nl&quot;&gt;top&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;m&quot;&gt;20px&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;nl&quot;&gt;left&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;m&quot;&gt;15px&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;}&lt;/span&gt;
    &lt;span class=&quot;nc&quot;&gt;.contact&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;.flip.backCard&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;cursor&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;pointer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;nl&quot;&gt;background&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;m&quot;&gt;#f3f3f3&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;nl&quot;&gt;display&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;block&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;nl&quot;&gt;transform&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;rotate&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;m&quot;&gt;3deg&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;-o-transform&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;rotate&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;m&quot;&gt;3deg&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;err&quot;&gt;   &lt;/span&gt; &lt;span class=&quot;nl&quot;&gt;-ms-transform&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;rotate&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;m&quot;&gt;3deg&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt; &lt;span class=&quot;nl&quot;&gt;-moz-transform&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;rotate&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;m&quot;&gt;3deg&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt; &lt;span class=&quot;nl&quot;&gt;-webkit-transform&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;rotate&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;m&quot;&gt;3deg&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);}&lt;/span&gt;
    &lt;span class=&quot;nc&quot;&gt;.contact&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;.flip.backCard&lt;/span&gt;&lt;span class=&quot;nd&quot;&gt;:hover&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;background&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;m&quot;&gt;#ffffff&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;}&lt;/span&gt;
    &lt;span class=&quot;nc&quot;&gt;.contact&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;.cardWrapper&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;position&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;relative&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;nl&quot;&gt;min-height&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;m&quot;&gt;195px&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;nl&quot;&gt;width&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;auto&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;}&lt;/span&gt;
    &lt;span class=&quot;nc&quot;&gt;.contact&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;.spacer&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;height&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;m&quot;&gt;25px&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;nl&quot;&gt;display&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;block&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;nl&quot;&gt;clear&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;both&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;}&lt;/span&gt;
    &lt;span class=&quot;nc&quot;&gt;.contact&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;.flip&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;position&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;absolute&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;nl&quot;&gt;background-color&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;white&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;}&lt;/span&gt;
    &lt;span class=&quot;nc&quot;&gt;.contact&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;address&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;box-shadow&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;2px&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;2px&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;8px&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;0&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;#cccccc&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;nl&quot;&gt;padding&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;m&quot;&gt;10px&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;nl&quot;&gt;width&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;m&quot;&gt;300px&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;nl&quot;&gt;height&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;m&quot;&gt;150px&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;nl&quot;&gt;border&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;#eeeeee&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;solid&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;1px&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;nl&quot;&gt;float&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;none&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;nl&quot;&gt;overflow&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;hidden&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;err&quot;&gt;   &lt;/span&gt; &lt;span class=&quot;nl&quot;&gt;-moz-transition&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;none&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;-webkit-transition&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;none&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;-o-transition&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;none&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;transition&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;none&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;-ms-transition&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;none&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;}&lt;/span&gt;
    &lt;span class=&quot;nc&quot;&gt;.contact&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;address&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;.brokerPhoto&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;float&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;left&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;nl&quot;&gt;max-height&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;m&quot;&gt;100px&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;nl&quot;&gt;max-width&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;m&quot;&gt;75px&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;nl&quot;&gt;margin&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;15px&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;10px&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;0&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;nl&quot;&gt;display&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;block&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;}&lt;/span&gt;
    &lt;span class=&quot;nc&quot;&gt;.contact&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;.brokerName&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;font-size&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;m&quot;&gt;16px&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;nl&quot;&gt;font-weight&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;normal&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;nl&quot;&gt;margin-top&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;m&quot;&gt;15px&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;nc&quot;&gt;.contact&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;img&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;.brokerPhoto&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;~&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;ul&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;max-width&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;m&quot;&gt;190px&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;nc&quot;&gt;.contact&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;.orglogo&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;img&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;position&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;absolute&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;nl&quot;&gt;bottom&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;m&quot;&gt;10px&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;nl&quot;&gt;right&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;m&quot;&gt;10px&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;max-height&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;m&quot;&gt;100px&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;nl&quot;&gt;max-width&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;m&quot;&gt;100px&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;nc&quot;&gt;.contact&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;.moreContacts&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;display&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;none&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;}&lt;/span&gt;
    &lt;span class=&quot;nc&quot;&gt;.contact&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;img&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;.moreContacts&lt;/span&gt;&lt;span class=&quot;err&quot;&gt; &lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;float&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;right&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;nl&quot;&gt;position&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;absolute&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;nl&quot;&gt;top&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;m&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;nl&quot;&gt;right&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;m&quot;&gt;-12px&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;nl&quot;&gt;display&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;block&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;nl&quot;&gt;cursor&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;pointer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;nl&quot;&gt;width&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;m&quot;&gt;14px&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;nl&quot;&gt;height&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;m&quot;&gt;22px&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;nc&quot;&gt;.contact&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;.ul&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;float&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;left&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;nl&quot;&gt;display&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;block&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;h3 id=&quot;customized-business-card&quot;&gt;Customized business card&lt;/h3&gt;

&lt;p&gt;&lt;img src=&quot;/images/2011-10-14-make-real-estate-brokers-happy-with-hard-boiled-business-cards/card_customized1.png&quot; alt=&quot;Card&quot; /&gt;
This third layer of css forms the broker’s  customized business card:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-css&quot; data-lang=&quot;css&quot;&gt;    &lt;span class=&quot;nc&quot;&gt;.contact&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;color&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;m&quot;&gt;#46464a&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;nl&quot;&gt;font-size&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;m&quot;&gt;11px&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;}&lt;/span&gt;
    &lt;span class=&quot;nc&quot;&gt;.contact&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;.orglogo&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;display&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;none&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;}&lt;/span&gt;
    &lt;span class=&quot;nc&quot;&gt;.contact&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;.brokerPhoto&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;margin&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;m&quot;&gt;11px&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;20px&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;0&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;11px&lt;/span&gt; &lt;span class=&quot;cp&quot;&gt;!important&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;}&lt;/span&gt;
    &lt;span class=&quot;nc&quot;&gt;.contact&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;.brokerName&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;font-weight&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;bold&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;nl&quot;&gt;font-family&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&quot;Lucida Sans&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&quot;Lucida Sans Unicode&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&quot;Lucida Grande&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;sans-serif&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;nl&quot;&gt;margin&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;m&quot;&gt;8px&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;10px&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;0&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;nl&quot;&gt;font-size&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;m&quot;&gt;12px&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;}&lt;/span&gt;
    &lt;span class=&quot;nc&quot;&gt;.contact&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;.brokerTitle&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;margin&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;m&quot;&gt;0&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;0&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;5px&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;}&lt;/span&gt;
    &lt;span class=&quot;nc&quot;&gt;.contact&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;.brokerPhone&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;dt&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;font-weight&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;bold&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;}&lt;/span&gt;
    &lt;span class=&quot;nc&quot;&gt;.contact&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;.brokerMobil&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;dt&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;font-weight&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;bold&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;}&lt;/span&gt;
    &lt;span class=&quot;nc&quot;&gt;.contact&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;.brokerFax&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;dt&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;font-weight&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;bold&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;}&lt;/span&gt;
    &lt;span class=&quot;nc&quot;&gt;.contact&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;.brokerEmail&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;a&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;padding-top&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;m&quot;&gt;5px&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;nl&quot;&gt;display&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;inline-block&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;}&lt;/span&gt;
    &lt;span class=&quot;nc&quot;&gt;.contact&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;.brokerEmail&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;a&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;.contact&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;.homePage&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;a&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;color&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;m&quot;&gt;#46464a&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;}&lt;/span&gt;
    &lt;span class=&quot;nc&quot;&gt;.contact&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;.homePage&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;margin-top&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;m&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;}&lt;/span&gt;
    &lt;span class=&quot;nc&quot;&gt;.contact&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;.backgroundLogoLink&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;position&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;absolute&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;nl&quot;&gt;left&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;m&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;nl&quot;&gt;bottom&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;m&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;nl&quot;&gt;width&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;m&quot;&gt;320px&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;nl&quot;&gt;height&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;m&quot;&gt;25px&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;nl&quot;&gt;display&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;block&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;&lt;a href=&quot;http://blog.whatfettle.com/2010/01/14/hardboiled-hcards/&quot;&gt;Read more about hard boiled business cards and the hcard microformat&lt;/a&gt;
&lt;a href=&quot;http://hardboiledwebdesign.com/&quot;&gt;Dig deeply into hardboiled web design&lt;/a&gt;&lt;/p&gt;

&lt;h2 id=&quot;the-feedback&quot;&gt;The feedback&lt;/h2&gt;

&lt;p&gt;Three weeks after the release we have recieved 1060 feedbacks, 630 positive, 374 negative and 56 indifferent. This is actually better feedback than we are used to after releasing such a radical redesign. Our biggest challenge is to make it easier to view images in full size. For other layout issues it seems like people simply need some time to get used to it. I have seen no complaints from users who dislikes the business cards, which was one of our biggest concerns before the release.&lt;/p&gt;

&lt;p&gt;Our analyzing tools tell us that the average viewing time on this page before the release was 33 seconds. After the release of the new design the viewing time has increased to 1 minute and 15 seconds, at the expense of the image viewing page, which page views has dropped rapidly. The number of page views of the ad page has also dropped, probably because of less navigation to and from the image viewing page. However, the number of visits on the ad page seems to be stable.&lt;/p&gt;
</content>
        
        <author>
            <name>tom</name>
            
        </author>
        
    </entry>
    
    <entry>
        <title>How to get a job at FINN.no</title>
        <link rel="alternate" type="text/html" href="https://tech.finn.no2011/09/02/how-to-get-a-job-at-finn-no/"/>
        <updated>2011-09-02T08:08:46+00:00</updated>
        <id>https://tech.finn.no/2011/09/02/how-to-get-a-job-at-finn-no</id>
        <content type="html">&lt;p&gt;If you’re a student or just someone who is looking for a job you might be wondering: “How could I get to work for FINN.no?”. One guy found an answer and is now one of the key developers on our iOS applications. We did an advert for summer interns last year and he was one of the people who came in to do some proof of concept work on iOS. The end result was an iPad application that never hit the AppStore, but for one of the participant this internship was the first step towards landing a job as a developer at FINN.no.&lt;/p&gt;

&lt;p&gt;What made you apply for the summer internship at FINN.no?&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;It all started with my university teacher pointed out that FINN.no had an open internship during the summer. At first I did not want to apply, because I wanted to have vacation all summer (Like all other informatics students). However, I ended up with applying and just a few hours later I got a call; the internship was mine! The main reason to apply for the internship was that I was finishing my master’s degree and was lacking work experience in the field of informatics. Secondly, I did not know that much about FINN.no and I could always need the extra money :)&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;What did you learn during that summer?&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;I learned that FINN.no is not just a big website, it is also a big company. And there are not just developers at FINN.no, but also a range of different jobs (Like finance, marketing, economics and so on). When taking about the technical aspects I learned how to develop for the iPhone/iPad. Furthermore, I learned that to get things working you are depended on a range of different “teams” to develop what you need. And this is not always trivial, because all “teams” have their own focus areas and problems. So you never work independent!&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;What was it like to be a summer intern at FINN.no?&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;It was both interesting and rich on experiences. The one thing I want to point out is how the atmosphere and the peoples at FINN.no are. The atmosphere is comfortable and “chill” and the people are welcoming and easy to talk with. They want to teach you what they know and want to learn about what you know.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;How did you do about landing the position at FINN.no?&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;Firstly, I work hard during the summer and tried my best to present what I could do. Before I left FINN.no that summer I was told that if I needed a part time work, I should just ask and they would see what they could give me.  After the summer internship was completed I went back to school for a few months. Some time before Christmas I sent my supervisor at FINN.no a e-mail and ask if it was possible to get a 50% position at the company the last 6 months of my masters degree. 1. January I started with working part time at FINN.no and before I completed my degree I was presented with a full time position at FINN.no.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;What have you been doing since you came to work for FINN.no?&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;I have been continuing learning objective-c and iOS. Furthermore have I learned more about designing projects, completing them and how to work with other development teams (To get our hands on the right APIs we needed in the iOS app). One example is the new iOS app, FINN Torget annonse innleggning”, that FINN.no released a week ago.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;What is the difference between working with school assignments and working at a company?&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;The biggest difference must be that a school assignment is something that will never be used by any persons. So you just complete it because you must, and not because you want to. In a company as big as FINN.no your applications will be used by a lot of peoples. You must always perform your best to please all of the users. Because of this you want to stay on the edge of new technology and learn more.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;What’s your advice for other young developers out there wanting to work for the coolest company in Norway?&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;You must work hard and make them see you in the crowd. If a new internship opens up do not think twice just apply. Before you know it, you are placed in my position to answer a few questions in a interview like this one.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Anything you want to add?&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;I love how they work at FINN.no, a lot of very smart people and it feels like peoples at FINN.no don’t stress that much!
And peoples at FINN.no do know how to create a party ;)&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;img src=&quot;http://imgs.xkcd.com/comics/extended_mind.png&quot; alt=&quot;Extended mind XKCD&quot; /&gt;&lt;/p&gt;
</content>
        
        <author>
            <name>espen</name>
            
            <email></email>
            
        </author>
        
    </entry>
    
    <entry>
        <title>It has never been about the standers, while it has always been about the standards</title>
        <link rel="alternate" type="text/html" href="https://tech.finn.no2011/08/26/it-has-never-been-about-the-standers-while-it-has-always-been-about-the-standards/"/>
        <updated>2011-08-26T06:50:51+00:00</updated>
        <id>https://tech.finn.no/2011/08/26/it-has-never-been-about-the-standers-while-it-has-always-been-about-the-standards</id>
        <content type="html">&lt;p&gt;&lt;img src=&quot;http://www.midwestwebsense.com/av/wordle-med.png&quot; alt=&quot;Web Standards tag cloud&quot; /&gt;&lt;/p&gt;

&lt;p&gt;I was introduced to the World Wide Web back in 1995 while being a student at &lt;a href=&quot;http://www.himolde.no/&quot;&gt;Molde College, Norway&lt;/a&gt;. What fascinated me the most was that I could learn how to create stuff for the web by just viewing the source code of other web sites. This blew my mind and since then I have built a career working with web development.&lt;/p&gt;

&lt;p&gt;Creating web sites and web-based applications for me has always been about trying to adopt the latest technological advances in browsers, as soon as they were available. Naturally, this has lead to me reaching a few dead ends (IE’s &lt;a href=&quot;http://msdn.microsoft.com/en-us/library/ms531388&quot;&gt;Data Binding&lt;/a&gt; and &lt;a href=&quot;http://en.wikipedia.org/wiki/JavaScript_Style_Sheets&quot;&gt;Netscape JavaScript style sheets&lt;/a&gt;), but mostly it has enabled me to create some pretty cool things.&lt;/p&gt;

&lt;h1 id=&quot;from-dhtml-to-an-atomic-winter&quot;&gt;From DHTML to an atomic winter&lt;/h1&gt;

&lt;p&gt;In the days of the .com era, the community for creating web based applications was buzzing. We were cranking out all kinds of &lt;a href=&quot;http://en.wikipedia.org/wiki/Dynamic_HTML&quot;&gt;DHTML&lt;/a&gt;  magic. Every site out there was jumping on the band wagon without any hesitation. One of the classics is online retailer &lt;a href=&quot;http://en.wikipedia.org/wiki/Boo.com&quot;&gt;Boo.com&lt;/a&gt; which went all out with new technology without really succeeding. Naturally we had to deal with older browsers then too, but we did not let that get in the way of using the latest standards being adopted in browsers.&lt;/p&gt;

&lt;p&gt;The burst of the .com bubble created something of an atomic winter as far as web based user interface development goes. A lot of people lost their jobs and the rest of us took refuge in server side development.&lt;/p&gt;

&lt;h1 id=&quot;a-clean-slate-and-a-new-dawn&quot;&gt;A clean slate and a new dawn&lt;/h1&gt;

&lt;p&gt;Thankfully all the bad stuff was swiftly cleaned up with the introduction of the &lt;a href=&quot;http://en.wikipedia.org/wiki/Ajax_(programming)&quot;&gt;AJAX approach by Jesse James Garret in 2005&lt;/a&gt;. All the interface developers came out from their hiding places and they sure had been busy during that atomic winter. Out popped all kinds of amazing things like &lt;a href=&quot;http://dojotoolkit.org/&quot;&gt;Dojo Toolkit&lt;/a&gt;, Yahoo! UI Library and &lt;a href=&quot;http://jquery.com&quot;&gt;JQuery&lt;/a&gt;. Once more the web moved forward at a rapid pace thanks to the newly found fame due to the AJAX hype. JavaScript was getting recognition as an actual language and CSS was being implemented in a more serious manner than before.&lt;/p&gt;

&lt;p&gt;We all implemented our applications and sites using HTML, JavaScript and CSS2 and felt good about it. However it is worth noting that CSS2 was not a finished specification and &lt;a href=&quot;http://en.wikipedia.org/wiki/Cascading_Style_Sheets#Difficulty_with_adoption&quot;&gt;no browser has yet to implement the entire CSS2 specification&lt;/a&gt;. Still, we have used loads of what is in CSS2 for years.&lt;/p&gt;

&lt;h1 id=&quot;html5---a-new-generation-of-web-developers&quot;&gt;HTML5 - a new generation of web developers&lt;/h1&gt;

&lt;p&gt;Lulling in the wake of AJAX a new generation of web developers entered the scene. &lt;a href=&quot;http://www.whatwg.org/&quot;&gt;The WHATWG&lt;/a&gt; decided to take matters into their own hands and do what the W3C seemed incapable of, creating something which made creating web based applications with HTML easier. With the new hype of &lt;a href=&quot;http://en.wikipedia.org/wiki/HTML5&quot;&gt;HTML5&lt;/a&gt; we started to get all kinds of, what I thought, strange questions on blogs, &lt;a href=&quot;http://twitter.com&quot;&gt;Twitter&lt;/a&gt; and in industry media. “When can I start using HTML5?”, “When will it be ready?”, “I am so disappointed, all this hype and I can not use it”.&lt;/p&gt;

&lt;p&gt;All these questions struck me as very odd and I was beginning to think there was something wrong with me, had I missed something? I had never heard these kinds of questions before regarding CSS, JavaScript or HTML. Sure, we had to take into consideration that not all browsers supported everything in the same manner. But that was what we as web developer did, right? We create web based applications using the best technology available to us meanwhile making sure older less capable browsers could view it to. This is what got my career started and what I have spent a lot of time doing as a living.&lt;/p&gt;

&lt;p&gt;This new generation of developers seemed to be very focused upon only applying technology which where in standards that where finished.  To me this sounded weird as we have never once before had any hesitation as to adopt new technology when it was available to us. When the &lt;a href=&quot;http://en.wikipedia.org/wiki/XMLHttpRequest&quot;&gt;XmlHttpRequest&lt;/a&gt; object was missing in browsers, what did we do? We used the &lt;a href=&quot;http://ajaxpatterns.org/IFrame_Call&quot;&gt;old trick of the hidden IFrame&lt;/a&gt;. Back when &lt;a href=&quot;http://opera.com&quot;&gt;Opera&lt;/a&gt;’s JavaScript support was horrendous (luckily this is no longer the case) what did we do? We made sure our stuff worked without the use of JavaScript. If something was not a final specification or something was not implemented in all browsers we implemented fallback solutions or provided alternate experiences.&lt;/p&gt;

&lt;h1 id=&quot;our-bread-and-butter&quot;&gt;Our bread and butter&lt;/h1&gt;

&lt;p&gt;Creating web applications using the latest technologies available to us should be our bread and butter as web developers. We should not wait around for the W3C to put some stamp on some documents. Browser vendors drive innovation, not standards bodies and therefor we should apply the technology being made available to us as quickly as we can, while making sure those with less capable browsers also can access our service or site. HTML5 does not make this any harder than before and we should not use the lack of a finalized HTML5 specification hold us back.&lt;/p&gt;

&lt;h1 id=&quot;web-standards-are-everything&quot;&gt;Web Standards are everything&lt;/h1&gt;

&lt;p&gt;You might be tempted into thinking that web standards are not that important, after all the browser vendors drive innovation. Innovation is done by the vendors, but the standards are extremely important as a way of making sure the web stays open and does not become fragmented. There are always numerous organizations trying to create their own special web, but thanks to the great work of the standard bodies the web has stayed open and will continue to do so.&lt;/p&gt;

&lt;p&gt;The web standards (such as &lt;a href=&quot;http://www.webstandards.org/&quot;&gt;The Web Standards Project&lt;/a&gt; and &lt;a href=&quot;http://www.whatwg.org/&quot;&gt;WHATWG&lt;/a&gt;) community has really blossomed in the years after the introduction of AJAX and developers are way more conscious about the importance open standards. Without the work of the people in various working groups and standard bodies we would not be where we are today. Web technology is being used in more innovative ways than ever before. Not only restricted to making content or applications for the web, but also on the server side or to create native mobile applications.&lt;/p&gt;

&lt;p&gt;In summary the web standards are not important, but still they are essential for us to continue to enjoy all the amazing service and applications created with open web technology.&lt;/p&gt;

&lt;p&gt;This article was published in the August issue of the NDC Magazine from Programutvikling AS&lt;/p&gt;
</content>
        
        <author>
            <name>espen</name>
            
            <email></email>
            
        </author>
        
    </entry>
    
    <entry>
        <title>Putting numbers that goes with the face we put on quality</title>
        <link rel="alternate" type="text/html" href="https://tech.finn.no2011/07/04/putting-numbers-that-goes-with-the-face-we-put-on-quality/"/>
        <updated>2011-07-04T06:50:39+00:00</updated>
        <id>https://tech.finn.no/2011/07/04/putting-numbers-that-goes-with-the-face-we-put-on-quality</id>
        <content type="html">&lt;p&gt;In the post &lt;a href=&quot;http://tech.finn.no/2011/05/09/putting-a-face-on-quality/&quot;&gt;Putting a face on quality&lt;/a&gt; we talked about how we wanted to bring our end users closer to our developers by visualizing tweets about our service. This was only one of many tasks we performed in order to get a better feel on how our work effects our users. In this post we’ll show you what we did in order to put numbers on end user quality by working with out customer service team.&lt;/p&gt;

&lt;p&gt;Up until now our customer service team had a hard time visualizing the effect our code had on end users. They where publishing numbers on number of incoming requests, but these numbers where on a weekly basis at best.&lt;/p&gt;

&lt;h1 id=&quot;research-in-the-wild&quot;&gt;Research in the wild&lt;/h1&gt;

&lt;p&gt;Therefor we set out with the goal of making all support requests visible in real time for the entire organization. Out hypothesis was that this would provide everyone on our teams from developers, product owners and sales staff with a clear view on what kind of quality they deliver.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/2011-07-04-putting-numbers-that-goes-with-the-face-we-put-on-quality/desk02.png&quot; alt=&quot;Desk&quot; /&gt;&lt;/p&gt;

&lt;p&gt;In order to figure out what our application should look like we set out to see what the reality was and sat with our customer service team to see how they worked. But they had no system in place for logging requests and what area of our service it was about. All we had was the statistics from our support ticket system and call center. These systems did not provide enough detailed information, therefor our Lean task force team encouraged them to start logging requests on paper then put it all into a set of Excel spread sheets. Needless to say this required quite a bit of effort from the people one the customer service team and it was not a solution for the long term.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/2011-07-04-putting-numbers-that-goes-with-the-face-we-put-on-quality/desk01.png&quot; alt=&quot;Desk&quot; /&gt;&lt;/p&gt;

&lt;p&gt;When deciding how to approach creating a application for logging requests we looked at how the analog logging process was performed. Realizing that a request could be for more than one problem area and effect multiple teams we realized that the paper layout was in fact ideal. We created a quick prototype in HTML and had the users test it. This prototype had no persistence and did only show how the process of logging a request would look and feel. We didn’t create anything else as this would’ve been waste if we where not to proceed with the application. By not building anything besides what was absolutely necessary we where able do deliver quickly (3-4 days) and we had no inventory which would become waste if our prototype didn’t match what our end users required.
For simultaneous testing of the prototype we compared against an off the shelf product which used a wizard style approach to logging requests. Both prototypes was tested by the customer service team and the results where clear: the flexibility of the paper layout performed way better as it was quick and non-intrusive.&lt;/p&gt;

&lt;h1 id=&quot;the-penalty-of-success&quot;&gt;The penalty of success&lt;/h1&gt;

&lt;p&gt;With the success of the prototype we were now expected to deliver a solution which actually persisted the data and had a set of reports which would help visualize the requests coming in from our customers.&lt;/p&gt;

&lt;p&gt;The application has to be delivered to the team actually responsible for all our back office applications and so this put some requirements on which technologies to use. We develop most of our services with Java so this would be the choice for the back end. On our mobile web solution m.finn.no we use RestEasy to provide a REST interface for our one-page JavaScript front end, therefor this would be the choice for our application as well. Along with Spring/Spring JDBC and a MySQL database this was our back end. We specifically opted for conservative technology choices as we wanted to hand over to be as smooth as possible.
On the client side we opted to do a one page application written in JavaScript with the help of jQuery and a custom made publish-subscribe library along with Mustache for templating in our JS code.&lt;/p&gt;

&lt;h1 id=&quot;your-friendly-neighborhood-log-application&quot;&gt;Your friendly neighborhood log application&lt;/h1&gt;

&lt;p&gt;The only requirement from the customer was that it had to be effortless to log entries into our system. “There should be no reason not to log entries into the system”. We adopted what we call a “angstfri brukeropplevelse”, which translates into something like an anguish-free user experience.
Everything else was pretty much up to us to decide. The grid based layout inherited from the analog log was a success. Therefor all we added was keyboard navigation and a cross hair behavior which provided the user with an easier way of seeing which column they where in to help avoid invalid log entries. We specifically avoided any kind of validation or other kinds of business rules as this would only make the threshold for logging a bit higher and this was not acceptable.&lt;/p&gt;

&lt;p&gt;Having added a back-end and enhanced the user experience we did an initial test as soon as possible to get feedback on reports. A couple of tries later we where in production.&lt;/p&gt;

&lt;h1 id=&quot;quality--visibility--skills&quot;&gt;Quality ~= Visibility + Skills&lt;/h1&gt;

&lt;p&gt;Why did our architects set out to create an application for our customer service team you may ask? We focus upon enhancing the quality of what we deliver to our users and customers. Making sure everyone in our organization knows exactly how our work affect our users is one of the most important ways to make sure quality is always at the forefront in everything we do. This is why we used a couple of weeks to create this application. To quote some of the engineers at Facebook: “we want to bring your mother close to our developers”. We want everyone to feel that their mom is their users and who wants their mom to experience bugs or errors?&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/2011-07-04-putting-numbers-that-goes-with-the-face-we-put-on-quality/awesomeboard.png&quot; alt=&quot;Awesomeboard&quot; /&gt;
In order to make things visible we needed to iterate over our previous solution which visualized tweets about us on Twitter. We did a redesign and integrated some key figures into the application and the end result became really awesome, hence the name Awesome Board II.
There is still one more piece of the plan for visualizing quality and it will be discussed in a later post. However you can view it on some of our pages already.&lt;/p&gt;
</content>
        
        <author>
            <name>espen</name>
            
            <email></email>
            
        </author>
        
    </entry>
    
    <entry>
        <title>Graded browser support version 1.1</title>
        <link rel="alternate" type="text/html" href="https://tech.finn.no2011/06/27/graded-browser-support-version-1-1/"/>
        <updated>2011-06-27T13:41:23+00:00</updated>
        <id>https://tech.finn.no/2011/06/27/graded-browser-support-version-1-1</id>
        <content type="html">&lt;p&gt;We have reworked our &lt;a href=&quot;http://finn-no.github.io/nettlesermatrisen/&quot;&gt;graded browser support matrix&lt;/a&gt; which was &lt;a href=&quot;/2011/01/07/graded-browser-support-at-finn-no/&quot;&gt;released last year&lt;/a&gt;. Following &lt;a href=&quot;http://lawsofsimplicity.com/&quot;&gt;the Laws of Simplicity&lt;/a&gt; we reduced the complexity in order to make it easier for everyone at our company to understand. We cut a lot of the version number into targeting latest for browsers which are frequently updated. There is also a &lt;a href=&quot;http://finn-no.github.io/nettlesermatrisen/&quot;&gt;support matrix for mobile phones&lt;/a&gt; which is targeting our new mobile web service &lt;a href=&quot;http://m.finn.no&quot;&gt;m.finn.no&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;In addition to updating the versions we have also made the decision to include tablets in the support matrix for our desktop verison of our service. This is because we are currently not looking at creating specialized versions for tablets and our service works just fine on the tablets currently hitting our site. Currently almost all of our trafic is coming from iPad’s, therefor Andoid powered devices are not supported.&lt;/p&gt;
</content>
        
        <author>
            <name>espen</name>
            
            <email></email>
            
        </author>
        
    </entry>
    
    <entry>
        <title>Hosting our first ever hackaton</title>
        <link rel="alternate" type="text/html" href="https://tech.finn.no2011/06/24/hosting-our-first-ever-hackaton/"/>
        <updated>2011-06-24T07:09:41+00:00</updated>
        <id>https://tech.finn.no/2011/06/24/hosting-our-first-ever-hackaton</id>
        <content type="html">&lt;p&gt;Inspired by seeing how much fun and how much innovation is going on at hackatons all over the world we finally go round to hosting one ourselves. The first ever hackaton event at FINN.no was hosted on the 22-23. of june. At FINN.no we are accustomed to working in cross functional teams, so it was only natural that we invited the entire company to attend our hackaton. In order not to scare people with a non-technical background we had to change the name, so we created what we call FINNovasjonsdagen (which translates into FINNovationday). We decided to keep this one open for only employees since this was our first shot at this.&lt;/p&gt;

&lt;h2 id=&quot;what-happened&quot;&gt;What happened?&lt;/h2&gt;

&lt;p&gt;In total there where 43 attendants spread out across 13 teams, where about half of the attendants had the opportunity to stay the entire time. After the 24 hours we invited the entire company to come and vote on which team had produced the coolest thing.&lt;/p&gt;

&lt;p&gt;Check out this time laps video which was shot with a web cam:&lt;/p&gt;

&lt;p&gt;The winners of the hackaton was awarded the price of 25 thousand NOKs and bragging rights until the next event. Team Meh created a global search for all of FINN.no fully integrated and almost ready to be rolled out into production. This has been something our users and employees have been supporting for years and to see that a team of three was able to crank it out in 24 hours was quite an accomplishment.&lt;/p&gt;

&lt;p&gt;Check out the blog[Gone] from the event for more pictures and more details about all the things created (&lt;a href=&quot;http://translate.google.com/translate?js=n&amp;amp;prev=_t&amp;amp;hl=en&amp;amp;ie=UTF-8&amp;amp;layout=2&amp;amp;eotf=1&amp;amp;sl=no&amp;amp;tl=en&amp;amp;u=http%3A%2F%2Fwww.finnovasjonsdagen.org%2Fblog%2F&amp;amp;act=url&quot;&gt;English translation of the blog&lt;/a&gt;).&lt;/p&gt;
</content>
        
        <author>
            <name>espen</name>
            
            <email></email>
            
        </author>
        
    </entry>
    
    <entry>
        <title>Dependency Injection with constructors?</title>
        <link rel="alternate" type="text/html" href="https://tech.finn.no2011/05/12/dependency-injection-with-constructors/"/>
        <updated>2011-05-12T23:01:00+00:00</updated>
        <id>https://tech.finn.no/2011/05/12/dependency-injection-with-constructors</id>
        <content type="html">&lt;p&gt;&lt;img src=&quot;/images/2011-05-12-dependency-injection-with-constructors/neo.jpg&quot; alt=&quot;Pic of Neo/The Matrix&quot; /&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;em&gt;The debate whether to use
constructors, setters, fields, or interfaces
for &lt;a href=&quot;http://martinfowler.com/articles/injection.html&quot;&gt;dependency injection&lt;/a&gt; is often heated and opinionated.
Should you have a preference?&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;h2 id=&quot;the-argument-for-constructor-injection&quot;&gt;The argument for Constructor Injection&lt;/h2&gt;

&lt;p&gt;We had a consultant working with us reminding us to take a preference towards Constructor injection. Indeed we had a large code base using predominantly setter injection because in the past that is what the Spring community recommended.&lt;/p&gt;

&lt;p&gt;The arguments for constructor injection goes like:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;
    &lt;p&gt;Dependencies are &lt;em&gt;declared&lt;/em&gt; public, providing clarity in the wiring of &lt;strong&gt;Dependency Inversion&lt;/strong&gt;,&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;strong&gt;Safe construction&lt;/strong&gt;, what must be initialised must be called,&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;strong&gt;Immutability&lt;/strong&gt;, fields can be declared final, and&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;Clear &lt;strong&gt;indication of complexity&lt;/strong&gt; through numbers of constructor parameters.&lt;/p&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And that Setter injection can be used when needed for cyclic dependencies, optional and re-assignable dependencies, to support multiple/complicated variations of construction, or to free up the constructor for polymorphism purposes.&lt;/p&gt;

&lt;p&gt;Being a big fan of Inversion of Control but not overly of Dependency Injection frameworks something smelt wrong to me. Yet solely within the debate of constructor versus setter injection i don’t disagree that constructor injection has the advantage. Having been using Spring’s dependency injection through annotation a little recently and building a favouritism towards field injection I was happy to get the chance to ponder it over, to learn and to be taught new things. What was it i was missing? Is there a bigger picture?&lt;/p&gt;

&lt;h2 id=&quot;api-vs-implementation&quot;&gt;API vs Implementation&lt;/h2&gt;

&lt;p&gt;If there is a bigger picture it has to be around the Dependency Inversion argument since this is known to be potentially complex. The point here of using constructor injection is that &lt;em&gt;1)&lt;/em&gt; through a public declaration and injection of dependencies we build an explicit graph showing the dependency inversion throughout the application, and &lt;em&gt;2)&lt;/em&gt; even if the application is wired magically by a framework such injection must still be done in the same way without the framework (eg when writing tests).  The latter &lt;em&gt;(2)&lt;/em&gt; is interesting in that the requirement on “dependency injection” is too also inverted, that the framework providing dependency injection is removed from the architectural design and becomes solely a implementation detail. But it is the graph in &lt;em&gt;(1)&lt;/em&gt; that becomes an important facet in the following analysis.&lt;/p&gt;

&lt;p&gt;With this dependency graph in mind what does happen when we bring into the picture a desire to distinguish between API and implementation design…&lt;/p&gt;

&lt;p&gt;The DI graph being requested to be clarified by using constructor injection will fall into one of two categories:
&lt;strong&gt;→&lt;/strong&gt; ‘implementation-specific’, an interface defines the public API and the DI is held private by the constructor in the implementation class,
&lt;strong&gt;→&lt;/strong&gt; ‘API-specific’ when the class has no interface. Here everything public is a fully exposed api. There is no implementation-protected visibility here for injectable constructors.&lt;/p&gt;

&lt;p&gt;By introducing the constraint of only ever using constructor based injection: in the pursuit of a clarified dependency graph; you remove or make more difficult the ability to publicly distinguish between API and Implementation design.&lt;/p&gt;

&lt;p&gt;This distinction between API and implementation is important in being able to create the simple API. The previous blog “&lt;a href=&quot;http://tech.finn.no/2011/01/19/using-the-constretto-configuration-factory/&quot;&gt;using the constretto configuration factory&lt;/a&gt;” is a co-incidental example of this. I think the work in Constretto has an excellent implementation design to it, but this particular issue raised frustrations that the API was not as simple as it could have been. Indeed: to obtain the “simplest api”; Constretto (intentionally or not) promotes the use of Spring’s injection, a loose coupling that can be compared to reflection. It may be that our usage of Constretto’s API, where we wanted to isolate groups of properties, was not what the author originally intended but this only re-enforces the need for designing the simplest possible API.&lt;/p&gt;

&lt;p&gt;Therefore it is important to sometimes have all dependency injection completely hidden in the implementation. A clean elegant API must take precedence over a clean elegant implementation. And to achieve this one must first make that distinction between API and Implementation design.&lt;/p&gt;

&lt;p&gt;Taking this further we can introduce the distinction between &lt;a href=&quot;http://wiki.apidesign.org/index.php?title=APIvsSPI&amp;amp;useskin=monobook&quot;&gt;API and SPI&lt;/a&gt;. Here a good practice is to stick to using final classes for API and interfaces for SPI. By the same argument as above SPI can’t use constructor injection because they don’t have constructors.&lt;/p&gt;

&lt;h2 id=&quot;inversion-of-control-vs-dependency-injection&quot;&gt;Inversion-of-Control vs Dependency-Injection&lt;/h2&gt;
&lt;p&gt;What about the difference between IoC and DI. They are overlapping concepts: the subtlety between the “the contexts” and “the dependencies” rarely emphasised enough. (Java EE 6 has tried to address the distinction between contexts and dependencies at the implementation level with the &lt;a href=&quot;http://www.oracle.com/technetwork/articles/java/cdi-javaee-bien-225152.html&quot;&gt;CDI spec&lt;/a&gt;.) The difference between the two, nuanced as it may be, can help illustrate that the DI graph in any application deserves attention in multiple dimensions.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/2011-05-12-dependency-injection-with-constructors/request-stack.png&quot; alt=&quot;Request stack&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Drawing an application’s architecture up as a graph where the vertical axis represents the request stack: that which is typically categorised into architectural layers view, control, and model/services; and the horizontal axis representing the broadness of each architectural layer, then it can be demonstrated that:
&lt;strong&gt;→&lt;/strong&gt; IoC generally forms the passing and layering of contexts downwards.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;→&lt;/strong&gt; The &lt;em&gt;api-specific&lt;/em&gt; DI is fulfilling the layer of such contexts, and these contexts can be dependencies directly or helper classes holding such dependencies. Such dependencies must therefore be initially defined high up in the stack.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;→&lt;/strong&gt; The DI that is &lt;em&gt;implementation-specific&lt;/em&gt; is at most only visible inside each architectural layer and is the DI that is represented horizontally on the graph. Possibly still within the definition of IoC it can also be considered a “wiring of collaborating components”. The need for clarity in the dependency graph isn’t as critical and so applications here often tend towards Service Locators, Factories, and Injectable Singletons. On the other hand many of the existing Service Locator implementations have been poor enough to push people towards (and possibly it was an instigator for the initial implementations of) dependency injection.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;→&lt;/strong&gt; Constructor injection works easily horizontally, especially when instantiation of objects is under one’s ability, but has potential hurdles when working vertically down through the graph. Sticking to constructor injection horizontally can also greatly help when the wiring of an application is difficult, by ensuring at the construction of each object dependency injection has been successful. Missing setter, field, interface injection and Service Locators may not report an error until actually used in runtime.&lt;/p&gt;

&lt;p&gt;A simple illustration of difficulty with vertical constructor injection is looking at these helper contexts and how they may be layering contexts through delegation rather than repetitive instantiation, a pattern more applicable for an application with a deep narrow graph. This exemplifies a pattern that has often relied on proxy classes.&lt;/p&gt;

&lt;p&gt;Another illustration is when having to instantiate the initial context at the very top of the request/application stack it involves instantiating all the implementation of dependencies used in contexts down through the stack, this is when dependency inversion explodes - the case where the IoC becomes up-front and explicit, and the encapsulation of implementation is lost through an unnecessary leak of abstractions. A problem paralleling to this is trying to apply checked exceptions up through the request stack: one answer is that we need different checked exceptions per architectural layer (another answer is &lt;a href=&quot;http://www.cs.kuleuven.be/publicaties/rapporten/cw/CW544.pdf&quot;&gt;anchored exceptions&lt;/a&gt;). With dependencies we would eventuate with requiring different dependency types per architectural layer and this could lead to dependencies types from inner domains needing to be declared from the outer domains. Here we can instead declare &lt;em&gt;resource loaders&lt;/em&gt; in the initial context and then letting each architectural layer build from scratch its own context with dependencies constructed from configuration. But this comes the full circle in coming back to a design similar to a &lt;em&gt;service locator&lt;/em&gt;. Something similar has happened with annotations in that by bringing Convention over Configuration to DI what was once loose wiring with xml has become the magic of the convention and begins too to resemble the &lt;em&gt;service locator&lt;/em&gt; or &lt;em&gt;naming lookups&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/2011-05-12-dependency-injection-with-constructors/rabbits-matrix.jpg&quot; alt=&quot;follow the white rabbit/The Matrix&quot; /&gt;&lt;/p&gt;

&lt;p&gt;For a legacy application this likely becomes all too much: the declaring of all dependencies required throughout all these contexts; and so relying on a little louse-coupling-magic (be it reflection or spring injection) is our answer out. Indeed this seems to be one of the reasons spring dependency injection was introduced into FINN.
&lt;em&gt;And so we’ve become less worried about the type of injection used…&lt;/em&gt;&lt;/p&gt;

&lt;h2 id=&quot;broad-vs-deep-applications&quot;&gt;Broad vs Deep Applications&lt;/h2&gt;

&lt;p&gt;&lt;a href=&quot;http://www.finn.no&quot;&gt;FINN.no&lt;/a&gt; is generally a broad application with a shallow contextual stack. Here is the traditional view-control-model design and the services inside the model layer typically interact directly with the data stores and maybe interact with one or two peer services.&lt;/p&gt;

&lt;p&gt;Focusing on the interfaces to the services we see there is a huge amount of public api available to the controller layer and very little in defined contexts except a few parameters, or maybe the whole parameter map, and the current user object. There is therefore very little inversion of control in our contexts, it is often just parameterisation. (Why we often use interfaces to define service APIs is interesting since we usually have no intention for client code to be supplying their own implementations, it is definitely not SPIs that are being published. Such interfaces are used as a poor-man’s simplification of the API declaration of public methods within the final classes. Albeit these interfaces do make it easy to make stubs and mocks for tests.)&lt;/p&gt;

&lt;p&gt;In this design the implementation details of service-layer dependencies is rarely passed down through contexts but rather hard baked into the application. And in a product like FINN it probably always will be hard baked in. Hard baked here doesn’t mean it can’t be changed or mocked for testing, but that it is not a dynamic component, it is not contextual, and so does not belong in the architectural design of the application.&lt;/p&gt;

&lt;p&gt;In such a broad architectural layer i can see two problems in trying to obtain a perfect DI graph:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;→&lt;/strong&gt; cyclic dependencies: bad but forgiven when existing as peers within a group. In this case constructor injection fails. We can define one as the lesser or auxiliary service and fall-back to the setter/field injection just for it, but if they are real equal peers this could be a bullet-in-the-foot approach and using field injection for both with documentation might be the better approach.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;→&lt;/strong&gt; central dependencies: these are the “core” dependencies used throughout the bulk of the services, the database connection, resource loaders, etc. If we enforce these to be injected via constructors then we in turn are enforcing a global-store of them. Such a global store would typically be implemented as a factory or singleton. Then what is the point of injection? Worse yet is that this could encourage us to start passing the spring application context around through all our services. A &lt;em&gt;service locator&lt;/em&gt; may better serve our purpose…&lt;/p&gt;

&lt;p&gt;Hopefully by now you’ve guessed that we really should be more interested in modularisation of the code. Breaking up this very broad services layer into appropriate groups is an easier and more productive first step to take. And during this task we have found discovering and visualising the DI graph is not the problem. Untangling it is. Constructor injection can be used to prevent these tangles, but so can tools like maven reporting and sonar. This shows the the DI graph is actually more easily visualised through the class’s import statements than through constructor parameters.&lt;/p&gt;

&lt;p&gt;With modularisation we can minimise contexts, isolate dependency chains, publish contextual inversion of control into APIs, declare interface-injection for SPIs, and move dependency injection into wired constructors.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/2011-05-12-dependency-injection-with-constructors/request-stack-2.png&quot; alt=&quot;Request stack&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;back-to-constructor-injection&quot;&gt;Back to Constructor injection&lt;/h2&gt;

&lt;p&gt;So it’s true that constructor injection goes beyond just DI in being able to provide some IoC. But it alone can not satisfy Inversion of Control in any application unless you are willing to overlook API and SPI design. DI is not a subset or union of IoC: it has uses horizontally and in loose-coupling configuration; and IoC is not a subset or union of DI: to insinuate such would mean IoC can only be implemented using spring beans leading to an application of only spring beans and singletons. In the latter case IoC will often become forgotten outside the application’s realm of DI.&lt;/p&gt;

&lt;p&gt;Constructor injection is especially valid when it’s desired for code to be used via both spring-injection and manual injection, and it does make test code more natural java code. But imagine manually injecting every spring bean in a oversized legacy broad-stack application using constructor injection without the spring framework - is this really a possibility, let’s be serious? What you would likely end up with is one massive factory with initialisation code constructing all services instead of the spring xml, and looking up using this factory every request. What’s the point here? This isn’t where IoC is supposed to take us.&lt;/p&gt;

&lt;p&gt;If code is being moved towards a distributed and modular architecture you should pay be aware on how it clashes with the DI fan club.&lt;/p&gt;

&lt;p&gt;If code is in development and you are uncertain if the dependency should be obtained through a service locator or declared public giving dependency inversion, and in the spirit of lean think it smart to not yet make the decision, then using field injection can be the practical solution.&lt;/p&gt;

&lt;p&gt;And just maybe you are not looking to push the Dependency Inversion out into the API and because you think of Spring’s ApplicationContext (or BeanFactory) as your application’s Service Locator, you use field injection as a method to automate service locator lookups.&lt;/p&gt;

&lt;p&gt;For the majority of developers for the majority of the time you will be writing new code, not caring about dependency injection trashing inversion of control, wanting lots of easy to write tests, and not be worrying about API design so it’s ok to have a healthy preference towards constructor injection…&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/2011-05-12-dependency-injection-with-constructors/Morpheus-Red-or-Blue-Pill-the-matrix.jpg&quot; alt=&quot;Pic of Morpheus/The Matrix&quot; /&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Keep questioning everything…&lt;/em&gt;
…by remaining focused on what is required from the code at hand we can be pragmatic in a world full of rules and recommendations. This isn’t about laziness or permitting poor code, but about being the idealist: the person that knows the middle way between the pragmatist and ideologue. By knowing: when what can, and for how long, be dropped; we can incrementally evolve complex code towards a modular design in a sensible, sustainable, and practical way.
In turn this means the programmer gets the chance to catch breath and remember &lt;a href=&quot;/putting-a-face-on-quality/&quot;&gt;paramount to their work is the people&lt;/a&gt;: those that will develop against the design and those end-users of the product.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;References:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;
    &lt;p&gt;Martin Fowler gives a great introduction into all this at &lt;a href=&quot;http://martinfowler.com/articles/injection.html&quot;&gt;martinfowler.com/articles/injection.html&lt;/a&gt;.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;Taking taking this further Jaroslav Tulach presents an interesting view on Dependency Injection in relations to API design at &lt;a href=&quot;http://wiki.apidesign.org/index.php?title=Dependency_Injection&amp;amp;useskin=monobook&quot;&gt;wiki.apidesign.org/index.php?title=Dependency_Injection&lt;/a&gt;&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;Another read that investigates IoC : &lt;a href=&quot;http://shaun.boyblack.co.za/blog/2009/05/01/constructor-injection-vs-setter-injection/&quot;&gt;Constructor Injection vs Setter Injection - Shaun Smith&lt;/a&gt;&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;What’s the difference between &lt;a href=&quot;http://neelzone.wordpress.com/2007/04/04/injection-and-inversion/&quot;&gt;Dependency Injection and Inversion of Control&lt;/a&gt;&lt;/p&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Credits:&lt;/strong&gt;
A large and healthy dose of credit must go to Kaare Nilsen for being a sparring partner in the discussion that lead up to this article.&lt;/p&gt;
</content>
        
        <author>
            <name>mick</name>
            
            <email></email>
            
        </author>
        
    </entry>
    
    <entry>
        <title>Putting a face on quality</title>
        <link rel="alternate" type="text/html" href="https://tech.finn.no2011/05/09/putting-a-face-on-quality/"/>
        <updated>2011-05-09T12:44:05+00:00</updated>
        <id>https://tech.finn.no/2011/05/09/putting-a-face-on-quality</id>
        <content type="html">&lt;p&gt;&lt;a href=&quot;http://www.flickr.com/photos/dolarz/&quot;&gt;
    &lt;img src=&quot;/images/2011-05-09-putting-a-face-on-quality/flickrface.jpg&quot; alt=&quot;Photo by dolarz&quot; /&gt;
&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The topic of ensuring quality in the services we provide is a much debated topic in our industry [&lt;a href=&quot;http://gojko.net/2011/04/27/visualising-quality-initial-ideas&quot;&gt;1&lt;/a&gt;]. It tends to focus upon things such as testing and how to best automate it to try and reach a state of zero defects. These initiatives are great and we should all pursue them in our daily work. These debates tend to be very focused upon the technical side and how to prevent the intrusion of defects in our build cycle. A result of the technical focus is that you become abstracted from the real issues at hand and it becomes yet another ideological debate (or even worse yet another pissing contest between believers and non-believers).&lt;/p&gt;

&lt;p&gt;At &lt;a href=&quot;http://www.finn.no&quot;&gt;FINN.no&lt;/a&gt; we try to always view things from an end-user perspective and technical quality is no different. So what does technical quality mean for our end users and do they really care?&lt;/p&gt;

&lt;h2 id=&quot;making-dreams-come-true&quot;&gt;Making dreams come true&lt;/h2&gt;

&lt;p&gt;A service such as &lt;a href=&quot;http://www.finn.no&quot;&gt;FINN.no&lt;/a&gt; is much more than just a site where you view classified ads. It is a service where everyday people make some of their dreams come true. They buy the house they have been dreaming of in order to start a family. It is a place where you buy things to keep your kids safe and comfortable. So in order to answer the previous question, do they care about quality? Naturally. Down time on a Sunday at &lt;a href=&quot;http://www.finn.no&quot;&gt;FINN.no&lt;/a&gt; prevents people from buying the house, the car or the boat of their dreams.&lt;/p&gt;

&lt;p&gt;You get the picture right? Quality of service means that we deliver on our promisse of being a marketplace you can rely on to make some of your dreams come true. Behind the discussions about unit-test coverage there is a family not getting their house, car or boat if you mess things up. When we debate whether to write tests up-front or do waterfall planning there is someone out there not getting their car sold. We get so caught up in technology some times that we actually think that it matters. In order to try and get more in touch with how our quality affects our users we realized we had to do something. When growing from a small company to a larger company you often end up lossing track of these things and we needed to correct this.&lt;/p&gt;

&lt;h2 id=&quot;continuous-improvements-aka-lean&quot;&gt;Continuous improvements a.k.a. Lean&lt;/h2&gt;

&lt;p&gt;At &lt;a href=&quot;http://www.finn.no&quot;&gt;FINN.no&lt;/a&gt; we have some people work solely with teaching us &lt;a href=&quot;http://en.wikipedia.org/wiki/Continuous_improvement_process&quot;&gt;continuous improvement&lt;/a&gt;. They try to help every team or department to learn the &lt;a href=&quot;http://en.wikipedia.org/wiki/Lean_software_development&quot;&gt;Lean&lt;/a&gt; way of working and resolving issues. By spreading their knowledge across the company they start a lot of cross-team-department-initiatives which would not happen without them.&lt;/p&gt;

&lt;p&gt;Bulding quality into your product is one of the essential things towards creating an amazing user experience, but the problem is how do you do this on a regular basis? There are numerous tools available for all kinds of testing and quality analysis of code and things like that. These tools are all good and you should use them. However, they all fail to bring you the one insight which is the most valueable yo your business: what does your users actually think about what you do.&lt;/p&gt;

&lt;p&gt;Our customer service and architectural teams have both been working to try and establish a way of working which ensure continuous improvement in what we do. This has resulted in an initiative to try and visualize how our level of quality effects our users and customers.&lt;/p&gt;

&lt;h2 id=&quot;finnback-or-tweet-board&quot;&gt;FINNback or Tweet Board&lt;/h2&gt;

&lt;p&gt;One easy way of putting a face on quality was to utilize the &lt;a href=&quot;http://dev.twitter.com/pages/streaming_api&quot;&gt;Twitter Streaming API&lt;/a&gt; which enables us to see what people think about our service. Thanks to the &lt;a href=&quot;http://dev.twitter.com/&quot;&gt;brilliant engineers at Twitter&lt;/a&gt; this was easily accomplished in just a few hours and we had a &lt;a href=&quot;http://nodejs.org&quot;&gt;node.js&lt;/a&gt; application which displayed tweets about us on monitors/TVs in our cantina and in our reception. Thanks to the awesomeness of the &lt;a href=&quot;http://dev.twitter.com/pages/streaming_api&quot;&gt;Twitter Streaming API&lt;/a&gt; we are able to pull both the profile picture, the profile background image and the number of followers into our application in order to give a more human feel to the tweets coming in. This was critical as it makes the tweets real as they come from real live people who we effect with our work. In the sample screenshot of the &lt;em&gt;FINNback&lt;/em&gt; application  we get a confirmation of something we are painfully aware of, &lt;a href=&quot;http://twitter.com/#!/AnetteBastnes/status/65843803675820032&quot;&gt;we should have an app&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;http://www.flickr.com/photos/dolarz/&quot;&gt;
    &lt;img src=&quot;/images/2011-05-09-putting-a-face-on-quality/feedback.jpg&quot; alt=&quot;Photo of Feedback board&quot; /&gt;
&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Doing this Twitter feed visualization is just a first step and we are working hard towards creating more real-time feedback visualization in order to make sure every one of our employees knows exactly how our users feel about what we do. We are confident that this is a great way of getting focus on technical quality and to make sure we work even harder to provide a service which makes our users dreams come true.&lt;/p&gt;
</content>
        
        <author>
            <name>espen</name>
            
            <email></email>
            
        </author>
        
    </entry>
    
    <entry>
        <title>XSS protection: who's responsibility?</title>
        <link rel="alternate" type="text/html" href="https://tech.finn.no2011/04/08/xss-protection-whos-responsibility/"/>
        <updated>2011-04-08T07:09:39+00:00</updated>
        <id>https://tech.finn.no/2011/04/08/xss-protection-whos-responsibility</id>
        <content type="html">&lt;p&gt;In a multi-tier application who can be responsible for XSS protection?
Must security belong to a dedicated team…or can it be a shared responsibility?
Today &lt;strong&gt;&lt;a href=&quot;http://en.wikipedia.org/wiki/Cross-site_scripting&quot;&gt;XSS protection&lt;/a&gt;&lt;/strong&gt; is typically tackled by front end developers.
Let’s challenge the status quo.&lt;/p&gt;

&lt;h2 id=&quot;new-applications-vs-legacy-applications&quot;&gt;New Applications vs Legacy Applications&lt;/h2&gt;

&lt;p&gt;For protection against &lt;em&gt;Stored XSS&lt;/em&gt; many applications have the luxury of ensuring any text input from the user, or from a CMS system, is made clean and safe before being written to database. Given a clean database the only XSS protection required is around request values, for example values from url parameters, cookies, and form data.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/2011-04-08-xss-protection-whos-responsibility/rack.jpg&quot; alt=&quot;Image by The Planet via Flickr -- http://www.flickr.com/photos/26388913@N05/4879419700&quot; /&gt;&lt;/p&gt;

&lt;p&gt;But some applications, especially legacy applications, are in a different boat. Databases typically have lots of existing data in many different formats and tables so it’s often no longer feasible to focus on protecting data on its way into the system. In this situation it is the front end developers that pay the price for the poor quality of backend data and are are left to protect everything. This often results in a napalm-the-whole-forest style of xss protection where every single variable written out in the front end templates goes through some equivalent of&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&amp;lt;c:out value=&quot;${someText}&quot;/&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;This makes sense but…   if you don’t have control is your only option to be so paranoid?&lt;/p&gt;

&lt;h2 id=&quot;a-messed-up-world&quot;&gt;A Messed up World&lt;/h2&gt;

&lt;p&gt;To illustrate the problem let’s create a simple example by defining the following service&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;  interface AdvertisementService{
    Advertisement getAdvertisement(long id);
  }

  interface Advertisement{
    /** returns plain text title */
    String getTitle();
    /** return description, which may contain html if isHtmlEnabled() returns true */
    String getDescription();
    /** indicates the description is html */
    boolean isDescriptionHtml();
  }
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The web application, having already fetched an advertisement in the control tier, somewhere would have a view template looking something like&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;    &amp;lt;div&amp;gt;
        &amp;lt;h1&amp;gt;&amp;lt;c:out value=&quot;${advertisement.title}&quot;/&amp;gt;&amp;lt;/h1&amp;gt;
        &amp;lt;p&amp;gt;
            &amp;lt;c:out value=&quot;${advertisement.description}&quot;
                      escapeXml=&quot;${!advertisement.descriptionHtml}&quot;/&amp;gt;
        &amp;lt;/p&amp;gt;
    &amp;lt;/div&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;img src=&quot;/images/2011-04-08-xss-protection-whos-responsibility/information.gif&quot; alt=&quot;information&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Here we add another dimension: simple escaping with c:out won’t work if you actually want to write html (and what is the safety and quality of such html data).&lt;/p&gt;

&lt;p&gt;When this service is used by different applications each with their own view templates, and maybe also exposed through web services, you end up no doubt protecting it over many times, system developers in the web services, and front end developers in each of the presentation layers… likely there will be confusion over the safety and quality of data coming from this service, and of course everyone will be doing it differently so nothing will be consistent.&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Is there a better way?&lt;/li&gt;
  &lt;li&gt;Can we achieve a consistent XSS protection in such an environment?&lt;/li&gt;
  &lt;li&gt;How many developers need to know about XSS and about the origin of the data being served?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In the above code if we can guarantee that the service &lt;em&gt;always&lt;/em&gt; returns safe data then we can simplify it by removing the isDecriptionHtml() method. The same code and view template would become&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;  interface AdvertisementService{
    Advertisement getAdvertisement(long id);
  }

  /** All fields xss protected */
  interface Advertisement{
    /** returns plain text title */
    String getTitle();
    /** return description, which may contain safe html */
    String getDescription();
  }



    &amp;lt;div&amp;gt;
        &amp;lt;h1&amp;gt;${advertisement.title}&amp;lt;/h1&amp;gt;
        &amp;lt;p&amp;gt;${advertisement.description}&amp;lt;/p&amp;gt;
    &amp;lt;/div&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;By introducing one constraint: that &lt;strong&gt;all data is xss protected in the services tier&lt;/strong&gt;; we have provided a simpler and more solid service API, and allowed all applications to have simpler, more concise, more readable, view templates.&lt;/p&gt;

&lt;h2 id=&quot;solutions&quot;&gt;Solutions&lt;/h2&gt;

&lt;p&gt;&lt;em&gt;Having a clean database&lt;/em&gt; all non-html data can be escaped as it comes in. Take advantage of Apache’s StringEscapeUtils.escapeHtml(..) from &lt;a href=&quot;http://commons.apache.org/lang/&quot;&gt;commons-lang&lt;/a&gt; library. For incoming html one can take advantage of a rich html manipulation tool, eg like JSoap, to clean, normalise, and standardise it.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;With legacy or foreign data&lt;/em&gt;, especially those applications with exposed service architecture and/or multiple front ends, a different approach is best: ensure nothing unsafe ever comes out of the services tier. For the html data the services will often be filtering many snippets of html over and over again, so this needs to be fast and a heavy html manipulation library like JSoap isn’t appropriate any more.
A suitable library is &lt;a href=&quot;http://finn-no.github.com/xss-html-filter&quot;&gt;xss-html-filter&lt;/a&gt;, a port from libfilter. It is fast and has an easy API&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;String safeHtml = new HTMLInputFilter().filter( &quot;some html&quot; );
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;If we do this it means
&lt;img src=&quot;/images/2011-04-08-xss-protection-whos-responsibility/lightbulb_on.gif&quot; alt=&quot;lightbulb on&quot; /&gt; xss protection is not duplicated, but rather made clear who is responsible for what,
&lt;img src=&quot;/images/2011-04-08-xss-protection-whos-responsibility/lightbulb_on.gif&quot; alt=&quot;lightbulb on&quot; /&gt; c:out becomes just junk in verbosity and performance* ,
&lt;img src=&quot;/images/2011-04-08-xss-protection-whos-responsibility/lightbulb_on.gif&quot; alt=&quot;lightbulb on&quot; /&gt; service APIs become simpler,
&lt;img src=&quot;/images/2011-04-08-xss-protection-whos-responsibility/lightbulb_on.gif&quot; alt=&quot;lightbulb on&quot; /&gt; view templates look the same for both new and legacy applications,
&lt;img src=&quot;/images/2011-04-08-xss-protection-whos-responsibility/lightbulb_on.gif&quot; alt=&quot;lightbulb on&quot; /&gt; system developers become responsible for protection of database data and this creates a natural incentive for them to clean up existing data and ensure new data comes in safe,&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;No matter what an inescapable fact is all front ends developers must have a concrete understanding that any value fresh from the request is prone to &lt;em&gt;Reflected XSS&lt;/em&gt; and there’s nobody but them that can be responsible for protecting these values.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;XSS protection has become a basic knowledge requirement for all developers&lt;/strong&gt;.
&lt;em&gt;Like much to do about security … it’s always only as strong as its weakest link&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;At FINN.no because we take security seriously, and because we know we are only human and we need some room for the occasional mistake, for each release we run security audits. These include using &lt;a href=&quot;http://www.watchcom.no/&quot;&gt;WatchCom&lt;/a&gt; reports, tools like &lt;a href=&quot;http://www.acunetix.com/&quot;&gt;Acunetix&lt;/a&gt;, and custom tests using &lt;a href=&quot;http://cukes.info/&quot;&gt;Cucumber&lt;/a&gt;.&lt;/p&gt;
</content>
        
        <author>
            <name>mick</name>
            
            <email></email>
            
        </author>
        
    </entry>
    
    <entry>
        <title>Multiple versions of Chrome on OS X</title>
        <link rel="alternate" type="text/html" href="https://tech.finn.no2011/04/07/multiple-versions-of-chrome-on-os-x/"/>
        <updated>2011-04-07T13:37:45+00:00</updated>
        <id>https://tech.finn.no/2011/04/07/multiple-versions-of-chrome-on-os-x</id>
        <content type="html">&lt;p&gt;As a professional web developer you should use the bleeding version of Google Chrome, so you’re prepared what your users will see a few weeks ahead. But the dev version tends to be a bit more buggy (naturally), and you have to test that your page works with both the beta and the stable version. Google Chrome has a command-line parameter to specify another profile, but it’s a bit tricky to add command-line parameters to a Mac application. Yesterday I found a blog-post by Duo Consulting. They have made a nice little script that will generate an app with the profile you specify. I modified it a bit to allow running different versions of Google Chrome as well as a profile for each of them.&lt;/p&gt;

&lt;p&gt;To make the install process even easier for others I’ve zipped the generated applications (they just contain a script).&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/2011-04-07-multiple-versions-of-chrome-on-os-x/chrome.png&quot; alt=&quot;Chrome&quot; /&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Disclaimer: Each version will have it’s own profile, so you have to set up each one from scratch with bookmarks, plugins and everything else. As Scott mentions in the comments, you can use the sync feature in Chrome to keep the installations in sync.&lt;/em&gt;&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;
    &lt;p&gt;First &lt;a href=&quot;http://www.chromium.org/getting-involved/dev-channel&quot;&gt;download each version&lt;/a&gt; and rename the original Google Chrome apps to “Google Chrome Stable”, “Google Chrome Beta” and “Google Chrome Dev” and put them in Applications &lt;strong&gt;under your user folder&lt;/strong&gt;.
I.e.: /Users/gregers/Applications/&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;Unzip Chrome-Trio.zip(File removed) and put the apps in /Applications&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;Don’t select any of them as default browser, since that will use the default profile instead of the custom. Instead start Safari -&amp;gt; Preferences -&amp;gt; General -&amp;gt; Default web browser -&amp;gt; Select… -&amp;gt; Go to /Applications and choose the version of Chrome you want as default :)&lt;/p&gt;
  &lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If you’re interested in the script I used to make the wrapper-apps. You can find it here: &lt;a href=&quot;http://pastebin.com/gPSdNi40&quot;&gt;http://pastebin.com/gPSdNi40&lt;/a&gt;&lt;/p&gt;
</content>
        
        <author>
            <name>gregers</name>
            
            <email></email>
            
        </author>
        
    </entry>
    
    <entry>
        <title>The Great Leap - getting serious with JavaScript at FINN.no</title>
        <link rel="alternate" type="text/html" href="https://tech.finn.no2011/04/01/the-great-leap-getting-serious-with-javascript-at-finn-no/"/>
        <updated>2011-04-01T11:45:39+00:00</updated>
        <id>https://tech.finn.no/2011/04/01/the-great-leap-getting-serious-with-javascript-at-finn-no</id>
        <content type="html">&lt;p&gt;&lt;img src=&quot;/images/2011-04-01-the-great-leap-getting-serious-with-javascript-at-finn-no/krafttak-cjno.png&quot; alt=&quot;Krafttak for JavaScript&quot; /&gt;&lt;/p&gt;

&lt;p&gt;FINN.no are blessed to be based in the same country as one of the brightest young stars when it comes to testing in JavaScript and applying Test Driven Development: &lt;a href=&quot;https://github.com/cjohansen&quot;&gt;Christian Johansen&lt;/a&gt; (the poster above was used to promote the event).
He works as a developer at Gitorious during the day time, but he also cranks out frameworks like &lt;a href=&quot;http://sinonjs.org&quot;&gt;SinonJS&lt;/a&gt; and he has even published a &lt;a href=&quot;http://www.amazon.com/dp/0321683919/&quot;&gt;book on how to do Test Driven Development in JavaScript&lt;/a&gt;.&lt;/p&gt;

&lt;h2 id=&quot;driving-your-design-with-tests&quot;&gt;Driving your design with tests&lt;/h2&gt;

&lt;p&gt;We first had an amazing live coding session which he had previously done at FrontTrends. The task was to create a type-ahead widget which sent requests to a server if the delay was more than 50 millies. Driving the design of an HTML widget with tests is just as awesome as doing the same thing with JUint on the back-end.  You get a nice set of really simple objects which provide you with a set of tools to create the functionality you want. This is very different from the one-object-with-everything kind of code you see a lot of when you do not focus upon design before you code. You can accomplish good clean code without&lt;/p&gt;

&lt;h2 id=&quot;refactor-some-legacy-code-with-tests&quot;&gt;Refactor some legacy code with tests&lt;/h2&gt;

&lt;p&gt;We have just recently ported parts of our platform related to &lt;a href=&quot;http://www.finn.no/finn/car/used/advanced&quot;&gt;advanced search&lt;/a&gt; to a new Java framework, but we did not port much of the JavaScript code. Only thing we have done is to prevent it from flooding the global scope and extract the JS code into a separate file. Christian gave us the challenge of trying to test this code. This was by no means an easy task.
The code was not written with testing in mind. It has some weird coding errors which for some reason does not produce errors in the user interface. Stuff like a function which one would expect to return a boolean, but when we tried to &lt;em&gt;assertTrue&lt;/em&gt; on the result of a validation it failed and we couldn’t see why.&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;function validateForm() {
    var valid = true;
    $(&quot;input.number&quot;).each(function(){
        valid &amp;amp;= validateInput($(this));
    });
    return valid;
    }
...
    if (validateForm()) { ....}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;That was until we noticed the  bitwise and operator used to indicate valid, which returns 0 or 1. Not true or false which you might expect since the valid variable is initialized with true.&lt;/p&gt;

&lt;h2 id=&quot;two-wrongs-make-a-right&quot;&gt;Two wrongs make a right?&lt;/h2&gt;

&lt;p&gt;To proceed with testing this advanced search field we had to assert whether the red border was on or not. This was the only thing visible from outside of the object. We hit a bit of a snag when it came to the css-function in jQuery, which it turns out does not return a color if you pass in just &lt;em&gt;border&lt;/em&gt;. However if you pass in &lt;em&gt;border-color-left&lt;/em&gt; you get the result &lt;em&gt;rgb(204, 0, 0)&lt;/em&gt;.&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;function validateInput(element) {
    if (val.length &amp;gt; 0 &amp;amp;&amp;amp; val.search(/^\d+$/)) {
        element.css({border:&quot;1px solid #CC0000&quot;});
        element.focus(function() {
            element.css({border:&quot;1px solid #BBBBCC&quot;});
        });
        return false;
    }
    return true;
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;These are just a few of the thing we needed to figure out in order to add a test harness to some of our legacy code. It is very very hard work and you really need some &lt;a href=&quot;http://en.wikipedia.org/wiki/Cojones&quot;&gt;cojones&lt;/a&gt; to start doing it.  It was truly inspiring to have Christian over and we just have no excuse anymore for not writing tests for our JavaScript code. This is a huge leap forward in terms of getting serious with our JavaScript code.&lt;/p&gt;
</content>
        
        <author>
            <name>espen</name>
            
            <email></email>
            
        </author>
        
    </entry>
    
    <entry>
        <title>Style sheet for touch devices</title>
        <link rel="alternate" type="text/html" href="https://tech.finn.no2011/03/10/style-sheet-for-touch-devices/"/>
        <updated>2011-03-10T13:05:53+00:00</updated>
        <id>https://tech.finn.no/2011/03/10/style-sheet-for-touch-devices</id>
        <content type="html">&lt;p&gt;It’s easy to include a style sheet for small-screen mobile devices, but what if you specifically want to target touch devices. Then you also need to include devices with larger screens. The iPad has a resolution of 1024x768, but many desktop users also have this resolution, so you can’t just increase the max-device-width in your CSS media query. I’ve tried to &lt;a href=&quot;http://stackoverflow.com/questions/2607248/optimize-website-for-touch-devices&quot;&gt;find a better solution to the problem&lt;/a&gt;, but none seems to exist yet.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/2011-03-10-style-sheet-for-touch-devices/touch2.jpg&quot; alt=&quot;Touch&quot; /&gt;
So the best solution I’ve found is to use JavaScript to detect if the browser has touch events. It’s a bit dangerous because Firefox and Chrome have support for touch, so you can’t be sure they don’t expose the touch events to devices without a touch screen. Fortunately this has &lt;a href=&quot;http://code.google.com/p/chromium/issues/detail?id=36415&quot;&gt;already happened&lt;/a&gt; in Google Chrome. The team decided to disable the touch events so the detection developers already used would continue to work.&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;http://twitter.com/#!/paul_irish&quot;&gt;Paul Irish&lt;/a&gt; has researched a bit for &lt;a href=&quot;http://modernizr.com&quot;&gt;Modernizr&lt;/a&gt; to find out what detection methods work best. You can of course use Modernizr, but if all you need is this test it’s not necessary to include the whole library. All you really need is this:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-javascript&quot; data-lang=&quot;javascript&quot;&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;ontouchstart&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;window&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;At first I did the test in the initialization code on document.ready, but then the style sheet will load too late. I definitely don’t like document.write in external scripts because then you can’t load them asynchronously in an easy manner. However, now it’s actually useful, since we want the style sheet to be written out and loaded before the page is visible.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-javascript&quot; data-lang=&quot;javascript&quot;&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;script&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;type&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;text/javascript&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;ontouchstart&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;window&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;nb&quot;&gt;document&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;write&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&amp;lt;link rel=&quot;stylesheet&quot; type=&quot;text/css&quot; media=&quot;only screen&quot; href=&quot;/css/touch.css&quot;&amp;gt;&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;sr&quot;&gt;/script&amp;gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Anyone have a better way?&lt;/p&gt;
</content>
        
        <author>
            <name>gregers</name>
            
            <email></email>
            
        </author>
        
    </entry>
    
    <entry>
        <title>Getting Geeky in Hønefoss</title>
        <link rel="alternate" type="text/html" href="https://tech.finn.no2011/03/03/geting-geeky-in-hc3b8nefoss/"/>
        <updated>2011-03-03T09:35:37+00:00</updated>
        <id>https://tech.finn.no/2011/03/03/geting-geeky-in-hc3b8nefoss</id>
        <content type="html">&lt;p&gt;FINN recently created a position called Technical Leader which is going to be our champions of technical excellence. One of the objects for this two-day field trip was to get everyone in the same room and share ideas and challenges from a technological point of view.
&lt;img src=&quot;/images/2011-03-03-geting-geeky-in-hc3b8nefoss/sundvollen.png&quot; alt=&quot;Sundvollen&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;quality-does-not-cost--it-pays&quot;&gt;Quality does not cost … it pays&lt;/h2&gt;

&lt;p&gt;Most companies talk about how they work hard to “produce quality”, but most companies also fail to follow through and make quality a focus for the entire organization. Our first session was about defining what quality means for different stakeholders such as: end users, our CEO, operations and product owners. These discussions lead us to a first draft of a checklist which will be used before each new release.&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;
    &lt;p&gt;No known bugs&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;Tested in every environment&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;Customer support is informed of important changes&lt;/p&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;walkn-talk-for-good-mental-health&quot;&gt;Walk’n talk for good mental health&lt;/h2&gt;

&lt;p&gt;The term &lt;a href=&quot;http://en.wikipedia.org/wiki/Walk_and_talk&quot;&gt;walk and talk&lt;/a&gt; is both a technique for making movies, but it is also &lt;a href=&quot;http://www.walkandtalk.com/&quot;&gt;a therapeutical technique&lt;/a&gt;. At FINN we practice this by randomly pairing two people who both have prepared a challenge they have in their daily work and then they walk and talk for about an hour. This was the first time this group did this exercise and it was a huge success. It was great to get out and talk about stuff to someone you do not get to talk to very often during the work day. As a team building exercise I think this is a great tool which is highly recommended!&lt;/p&gt;

&lt;h2 id=&quot;the-challenge-of--company-open-space-sessions&quot;&gt;The challenge of  company Open Space sessions&lt;/h2&gt;

&lt;p&gt;&lt;img src=&quot;/images/2011-03-03-geting-geeky-in-hc3b8nefoss/people.jpg&quot; alt=&quot;People&quot; /&gt;&lt;/p&gt;

&lt;p&gt;During day two we had three &lt;a href=&quot;http://en.wikipedia.org/wiki/Open-space_meeting&quot;&gt;open space sessions&lt;/a&gt; which was buzzing with excitement. Doing open spaces at conferences it is really inspiring and you learn stuff which you can bring back to your organization. Having open space sessions for just company employees has a different dynamic and can really be something to get your spirits down. When you do this inside a company you build up excitement during the sessions and along with the excitement comes expectations that “somebody needs to make this happen”. There is nothing in “open space theory” about how you can easily get more out of these session than just a lot of nice thoughts. This would resemble putting up a suggestions box and then do nothing with any of the suggestions. What we did was to get some people together after the session to see what actions needed to be taken after the sessions. Kind of like a retrospective where we looked at what came out of the open spaces. It would be interesting to hear what others do with their internal open space sessions.&lt;/p&gt;
</content>
        
        <author>
            <name>espen</name>
            
            <email></email>
            
        </author>
        
    </entry>
    
    <entry>
        <title>Developing for mobile is punk rock!</title>
        <link rel="alternate" type="text/html" href="https://tech.finn.no2011/02/01/developing-for-mobile-is-punk-rock/"/>
        <updated>2011-02-01T19:08:24+00:00</updated>
        <id>https://tech.finn.no/2011/02/01/developing-for-mobile-is-punk-rock</id>
        <content type="html">&lt;p&gt;&lt;img src=&quot;/images/2011-02-01-developing-for-mobile-is-punk-rock/finnfronten.png&quot; alt=&quot;finnfronten_thumb&quot; /&gt;&lt;/p&gt;

&lt;p&gt;The title of this post is of course a blatant lie. Mobile development is nothing but pain from here until eternity (no, not really). But, luckily the brave developers at FINN.no aren’t scared of a little pain and therefor we decided to do a mobile development trippel header gig to showcase some of the different approaches to mobile developement.Due to some kids falling ille we had to reduce the show to be a double header. It did not end up to make a difference as the guys doing the talks totally kicked ass!&lt;/p&gt;

&lt;p&gt;Oh and the picture on the left is a flyer we put up everywhere to market the event at the FINN.no headquarters. Finnfrontend is a name for one of our communities of practice dedicated to front-end development. Unfortunately it was reduced to a double header and we missed out on getting the low down on iOS development of native apps.&lt;/p&gt;

&lt;h2 id=&quot;mobile-web-on-mfinnno&quot;&gt;Mobile web on m.finn.no&lt;/h2&gt;

&lt;p&gt;Frank and Sven Inge from Team Mobile at FINN.no presented the &lt;a href=&quot;http://beta.m.finn.no&quot;&gt;beta for m.finn.no&lt;/a&gt;. As announced previously on labs.finn.no[Gone] the beta for our mobile web service is now available for testing and feedback. We are very excited about this application and we feel that it takes mobile web to a new level here in Norway.&lt;/p&gt;

&lt;p&gt;Frank presented some of the plans going forward with the beta (which I am not going to tell here). He also shared some of the experiences and pains with developing for mobile devices. Naturally there are quite a few challenges with compatibility and providing a consistent look and feel across multiple platforms. This has been the bread and butter for web developers for years and it is no surprise that it is hard on mobile devices.&lt;/p&gt;

&lt;p&gt;The Team also talked about some of the design decisions they have made and why they ended up solving a challenge a certain way. Choosing to display all information and letting the user scroll as opposed to applying the show/hide-style functionality we are used to from the desktop web seems like a really good choice.&lt;/p&gt;

&lt;p&gt;The mobile web solution looks awesome and we can not wait to see the final version! I can only say that it definitely will ship before the 17th of May!&lt;/p&gt;

&lt;h2 id=&quot;app-development-with-htmlcss-and-phonegap&quot;&gt;App development with HTML+CSS and Phonegap&lt;/h2&gt;

&lt;p&gt;&lt;a href=&quot;http://twitter.com/audungk&quot;&gt;Audun Kjelstrup&lt;/a&gt; from &lt;a href=&quot;http://nordaaker.com/&quot;&gt;Nordaker&lt;/a&gt; shared his experiences with mobile app development using HTML+CSS+JavaScript for multiple platforms using &lt;a href=&quot;http://www.phonegap.com/&quot;&gt;Phonegap&lt;/a&gt;. Drawing on his past experiences with developing &lt;a href=&quot;http://www.dolly.no/shop/nyheter/dollys_pizzabygger_naa_ogsaa_som_iphone_app&quot;&gt;the Dolly Dimple pizza configurator&lt;/a&gt; for iPhone and the NHO Conference app for iOS, Android and Symbian he covered some of the challenges with this kind of development.&lt;/p&gt;

&lt;p&gt;Audun recommended going for a web based app if you wanted a user experience which differed a lot form what comes out of the iOS UIKit. This was mainly due to the cost of development and the work required to create custom experiences such as &lt;a href=&quot;http://www.dolly.no/shop/nyheter/dollys_pizzabygger_naa_ogsaa_som_iphone_app&quot;&gt;the Dolly Dimple pizza configurator&lt;/a&gt;. He also pointed out that web based apps will always struggle to have the exact same snappyness and scroll smoothness as a native application. Creating a web based app for multiple platforms is hard work (no real surprise there) and having to deal with older Symbian based phones was a lot of pain (no real surprise there either), so you should consider what platform you target carefully.
The slides from this presentations can be &lt;a href=&quot;http://www.meetup.com/CocoaHeads-Oslo/files/&quot;&gt;downloaded from the Cocoa Heads Oslo Meetup&lt;/a&gt; where it was held some days ago.&lt;/p&gt;

&lt;p&gt;This was an awesome session with two great talks which highlighted some of the challenges with creating applications for mobile devices across multiple platforms. Thanks to Audun and Team Mobile!&lt;/p&gt;

</content>
        
        <author>
            <name>espen</name>
            
            <email></email>
            
        </author>
        
    </entry>
    
    <entry>
        <title>Using the Constretto Configuration Factory</title>
        <link rel="alternate" type="text/html" href="https://tech.finn.no2011/01/19/using-the-constretto-configuration-factory/"/>
        <updated>2011-01-19T18:07:02+00:00</updated>
        <id>https://tech.finn.no/2011/01/19/using-the-constretto-configuration-factory</id>
        <content type="html">&lt;p&gt;Development at FINN.no was quick to jump on the &lt;a href=&quot;http://constretto.org&quot;&gt;Constretto&lt;/a&gt; library. It’s a great addition to any application making it easy to handle different environments and profiles with just the one build.&lt;/p&gt;

&lt;p&gt;When using Constretto at FINN.no one problem we found with the API is that it creates very verbose client code when the same ini or properties file is accessed throughout various class files.&lt;/p&gt;

&lt;p&gt;For example a repetition of the following happens&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-java&quot; data-lang=&quot;java&quot;&gt;&lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;static&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;CONSTANT_A&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;ConstrettoBuilder&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;()&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;createIniFileConfigurationStore&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;()&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;addResource&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;DefaultResourceLoader&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;().&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;getResource&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;classpath:file.ini&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;))&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;done&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;()&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;getConfiguration&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;()&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;evaluateToString&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;CONSTANT_A&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;

&lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;static&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;CONSTANT_B&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;ConstrettoBuilder&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;()&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;createIniFileConfigurationStore&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;()&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;addResource&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;DefaultResourceLoader&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;().&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;getResource&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;classpath:file.ini&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;))&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;done&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;()&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;getConfiguration&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;()&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;evaluateToString&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;CONSTANT_B&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;One way around this is to use spring injection. This makes nice concise code but comes with two flaws: the constants can no longer be constant (they have to be member fields), and you have to lump all your properties together into one big “globals” store (days of f77 comes to mind). A third problem with this approach is that you’re passing Constretto’s context through Spring’s context, we don’t really want chained contexts but direct access to individual contexts if possible.&lt;/p&gt;

&lt;p&gt;So looking for a more elegant solution to all this we wrote a &lt;a href=&quot;http://constretto.jira.com/secure/attachment/10010/constrettoconfigurationfactory.java&quot;&gt;simple factory&lt;/a&gt;, that has now been accepted upsteam,  that provides the necessary convenience methods so the constants can be declared short, sweet, and simple&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-java&quot; data-lang=&quot;java&quot;&gt;&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;static&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;org&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;constretto&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;util&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;StaticlyCachedConfiguration&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;config&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;static&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;CONSTANT_A&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;config&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;classpath:file.ini&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;evaluateToString&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;CONSTANT_A&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;static&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;CONSTANT_B&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;config&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;classpath:file.ini&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;evaluateToString&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;CONSTANT_B&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;This also caches the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ConstrettoConfiguration&lt;/code&gt;s improving your application’s startup performance.&lt;/p&gt;

&lt;p&gt;This was taken from Constretto’s &lt;a href=&quot;http://constretto.jira.com/browse/CC-14&quot;&gt;issue CC-14&lt;/a&gt;&lt;/p&gt;

</content>
        
        <author>
            <name>mick</name>
            
            <email></email>
            
        </author>
        
    </entry>
    
    <entry>
        <title>Graded browser support at FINN.no</title>
        <link rel="alternate" type="text/html" href="https://tech.finn.no2011/01/07/graded-browser-support-at-finn-no/"/>
        <updated>2011-01-07T14:16:09+00:00</updated>
        <id>https://tech.finn.no/2011/01/07/graded-browser-support-at-finn-no</id>
        <content type="html">&lt;p&gt;Inspired by the kick-ass developers of Yahoo! and their work on Graded Browser Support (GBS) we at FINN.no decided to adopt graded browser support as a way to communicate what level of support we have for different combinations of browsers and platforms.&lt;/p&gt;

&lt;h2 id=&quot;creating-the-support-matrix&quot;&gt;Creating the support matrix&lt;/h2&gt;

&lt;p&gt;FINN is the largest site in Norway when it comes to traffic and we have a good framework for statistics. In order to create the support matrix we took the statistics for the most popular browsers and platform combinations and put them in a grid. This gave us a matrix showing us which browser/platform combinations we need to consider from a business perspective. Although this was a good start, we also needed to figure out what level of support we should provide each combination. Numbers and usage alone does not provide a good enough basis upon setting support levels.&lt;/p&gt;

&lt;h2 id=&quot;the-cost-of-support&quot;&gt;The cost of support&lt;/h2&gt;

&lt;p&gt;Having read the early drafts of the awesome book &lt;a href=&quot;http://jsninja.com/&quot;&gt;Secrets of a JavaScript ninja&lt;/a&gt; by &lt;a href=&quot;http://ejohn.org/&quot;&gt;John Resig&lt;/a&gt; we decided to follow his approach on creating a GBS matrix and perform a cost-benefit-analysis. We did was to do a quick survey among our developers in order to figure out what the costs where for supporting different user agents on certain platforms. The results are displayed in the chart below and shows our subjective opinions on how much effort is required to support a certain browser. Note that this chart will not look the same for everyone. It is will vary based on the skills of your developers, what browsers they work in, etc, etc.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/2011-01-07-graded-browser-support-at-finn-no/kostnyttenettleser.png&quot; alt=&quot;Kostnyttenettleser&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;support-levels&quot;&gt;Support levels&lt;/h2&gt;

&lt;p&gt;Our levels of support are slightly more simple than those of Yahoo! and we only provide three levels:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;
    &lt;p&gt;A-support: no visual or functional errors, all errors are reported and all features should be tested for each release.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;B-support: no functional errors, core features should be tested for each release, visual errors are only reported with a low priority&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;C-support: no functional errors in core features, all other errors are not reported.&lt;/p&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is how we created our GDS matrix and we hope this will inspire you to do the same for your shop. We are going to do a similar exercise when it comes to mobile, but this is still work in progress.&lt;/p&gt;

&lt;p&gt;The matrix is available on labs.finn.no (the url is no longer available).&lt;/p&gt;
</content>
        
        <author>
            <name>espen</name>
            
            <email></email>
            
        </author>
        
    </entry>
    
    <entry>
        <title>the ultimate view</title>
        <link rel="alternate" type="text/html" href="https://tech.finn.no2010/11/04/the-ultimate-view/"/>
        <updated>2010-11-04T18:11:57+00:00</updated>
        <id>https://tech.finn.no/2010/11/04/the-ultimate-view</id>
        <content type="html">&lt;blockquote&gt;
  &lt;p&gt;&lt;strong&gt;This article has been rewritten for Tiles-3.&lt;/strong&gt;&lt;br /&gt;
&lt;a href=&quot;http://tech.finn.no/the-ultimate-view-tiles-3/&quot;&gt;tech.finn.no/the-ultimate-view-tiles-3/&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;A story of getting the View layer up and running quickly in Spring, using Tiles with wildcards, fallbacks, and definition includes, to use the Composite pattern and Convention over Configuration providing a minimal ongoing xml changes.&lt;/p&gt;

&lt;h2 id=&quot;summary&quot;&gt;Summary&lt;/h2&gt;

&lt;p&gt;From the architect’s perspective you see Apache Tiles as rare exotic Italian marble sheets laid out exquisitely, while the same architect will see SiteMesh as the steel wiring stuck inside the concrete slab. The beauty of the tiles is always admired and a key component in creating an eye-catching surrounding.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/2010-11-04-the-ultimate-view/1351.jpg&quot; alt=&quot;1351&quot; /&gt;&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Summary&lt;/li&gt;
  &lt;li&gt;Background&lt;/li&gt;
  &lt;li&gt;Step 0: Spring to Tiles Integration&lt;/li&gt;
  &lt;li&gt;Step 1: Wildcards&lt;/li&gt;
  &lt;li&gt;Step 2: The fallback pattern&lt;/li&gt;
  &lt;li&gt;Step 3: Definition includes&lt;/li&gt;
  &lt;li&gt;When the Composite pattern is superior&lt;/li&gt;
  &lt;li&gt;Conclusion&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;background&quot;&gt;Background&lt;/h2&gt;

&lt;p&gt;At FINN.no we were redesigning our control and view layers. We, being the architectural team of six, had already decided on Spring-Web as a framework for the control layer due to its flexibility and a design for us providing better, simpler, migration path. For the front end we were a little unclear. In a department of ~60 developers we knew that the popular vote would lead us towards SiteMesh. And we knew why for practical purposes sitemesh gives the front end developer more flexibility and less xml editing. But we knew sitemesh has some serious shortcomings…&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/2010-11-04-the-ultimate-view/images.jpe&quot; alt=&quot;image&quot; /&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;SiteMesh shortcomings:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;from a design perspective the Decorator pattern doesn’t combine with MVC as elegantly as the Composite pattern does&lt;/li&gt;
  &lt;li&gt;requires to hold all possible html for a request in buffer requiring large amounts of memory&lt;/li&gt;
  &lt;li&gt;unable to flush the response before the response is complete&lt;/li&gt;
  &lt;li&gt;requires more overall processing due to the processing of all the potentially included fragments&lt;/li&gt;
  &lt;li&gt;does not guaranteed thread safety&lt;/li&gt;
  &lt;li&gt;does not provide any structure or organisation amongst jsps, making refactorings and other tricks awkward&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;One of the alternatives we looked at was &lt;a href=&quot;http://tiles.apache.org/&quot;&gt;Apache Tiles.&lt;/a&gt; It follows the Composite Pattern, but within that allows one to take advantage of the Decorator pattern using a ViewPreparer (URL not working anymore). This meant it provided by default what we considered a superior design but if necessary could also do what SiteMesh was good at. It already had integration with Spring, and the way it worked it meant that once the Spring-Web controller code was executed, the Spring’s view resolver would pass the ball onto Tiles letting it do the rest. This gave us a clear MVC separation and an encapsulation ensuring single thread safety within the view domain.&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;Yet the most valuable benefit Tiles was going to offer wasn't realised until we [started experimenting a little more...](/2010/11/04/the-ultimate-view/2/)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;step-0-spring-to-tiles-integration&quot;&gt;Step 0: Spring to Tiles Integration&lt;/h2&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;The first step was integrating [Tiles and Spring](http://static.springsource.org/spring/docs/3.0.x/spring-framework-reference/html/view.html#view-tiles) together. There exists tutorials on this already, it boils down to registering both a ViewResolver and a TilesConfigurer in your spring-web configuration.




    &amp;lt;bean id=&quot;viewResolver&quot; class=&quot;org.springframework.web.servlet.view.tiles2.TilesViewResolver&quot;&amp;gt;
	    &amp;lt;property name=&quot;order&quot; value=&quot;1&quot;/&amp;gt;
	    &amp;lt;property name=&quot;viewClass&quot; value=&quot;org.springframework.web.servlet.view.tiles2.TilesView&quot;/&amp;gt;
    &amp;lt;/bean&amp;gt;
    &amp;lt;bean id=&quot;tilesConfigurer&quot; class=&quot;org.springframework.web.servlet.view.tiles2.TilesConfigurer&quot;&amp;gt;
	    &amp;lt;property name=&quot;tilesInitializer&quot;&amp;gt;
		    &amp;lt;bean class=&quot;no.finntech.control.servlet.tiles.FinnTilesInitialiser&quot;/&amp;gt;
	    &amp;lt;/property&amp;gt;
    &amp;lt;/bean&amp;gt;




The TilesConfigurer hooks into the &quot;tilesInitializer&quot; class that you must supply yourself. This is the new way in Tiles-2 of providing the [configuration](http://tiles.apache.org/config-reference.html). Our FinnTilesInitialiser looks like




    public class FinnTilesInitialiser extends AbstractTilesInitializer {
        public FinnTilesInitialiser() {}

        @Override
        protected AbstractTilesContainerFactory createContainerFactory(TilesApplicationContext context) {
            return new FinnTilesContainerFactory();
        }
    }
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;step-1-wildcards&quot;&gt;Step 1: Wildcards&lt;/h2&gt;

&lt;p&gt;&lt;img src=&quot;/images/2010-11-04-the-ultimate-view/Screenshot1.png&quot; alt=&quot;create these files and folders&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Here we hit Tiles’ most obvious downfall: every single jsp included must be declared in a definition in the tiles.xml file. Anyone that ever tried Tiles-1 knows this bad smell.&lt;/p&gt;

&lt;p&gt;Tiles-2 though provides a way to avoid it by using &lt;a href=&quot;http://tiles.apache.org/framework/tutorial/advanced/wildcard.html&quot;&gt;wildcards&lt;/a&gt;, and when i hear people talking about tiles it’s clear this Tiles-2 “dynamic composite” paradigm hasn’t really caught on.&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;Let's declare a definition and template like (and create some files and folders as shown to the right):


//--tiles.xml
    &amp;lt;definition name=&quot;REGEXP:(.+)&quot; template=&quot;/WEB-INF/tiles/template.jsp&quot;&amp;gt;
	    &amp;lt;put-attribute name=&quot;meta&quot; value=&quot;/WEB-INF/tiles/{1}/meta.jsp&quot;/&amp;gt;
	    &amp;lt;put-attribute name=&quot;header&quot; value=&quot;/WEB-INF/tiles/{1}/header.jsp&quot;/&amp;gt;
	    &amp;lt;put-attribute name=&quot;body&quot; value=&quot;/WEB-INF/tiles/{1}/body.jsp&quot;/&amp;gt;
	    &amp;lt;put-attribute name=&quot;footer&quot; value=&quot;/WEB-INF/tiles/{1}/footer.jsp&quot;/&amp;gt;
    &amp;lt;/definition&amp;gt;






 //--template.jsp
    &amp;lt;!DOCTYPE html&amp;gt;
    &amp;lt;%@ prefix=&quot;tiles&quot; taglib uri=&quot;http://tiles.apache.org/tags-tiles&quot; %&amp;gt;
    &amp;lt;html&amp;gt;
    &amp;lt;head&amp;gt;
    &amp;lt;tiles:insertAttribute name=&quot;meta&quot;/&amp;gt;
    &amp;lt;/head&amp;gt;
    &amp;lt;body&amp;gt;
    &amp;lt;div id=&quot;header&quot;&amp;gt;&amp;lt;tiles:insertAttribute name=&quot;header&quot;/&amp;gt;&amp;lt;/div&amp;gt;
    &amp;lt;div id=&quot;body&quot;&amp;gt;&amp;lt;tiles:insertAttribute name=&quot;body&quot;/&amp;gt;&amp;lt;/div&amp;gt;
    &amp;lt;div id=&quot;footer&quot;&amp;gt;&amp;lt;tiles:insertAttribute name=&quot;footer&quot;/&amp;gt;&amp;lt;/div&amp;gt;
    &amp;lt;/body&amp;gt;
    &amp;lt;/html&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Now our Spring controller can return definitions that match the names of the folder containing meta, header, body, and footer.&lt;/p&gt;

&lt;p&gt;Now we that we have three ready to go definitions “cat”, “dog”, and “cow”. Spring controllers can now use them like this&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;    @Controller
    public class MyWebsite{
        @RequestMapping(&quot;/cat&quot;)
        public ModelAndView viewCat(){
            Map modelMap = new HashMap();
            //...
            return new ModelAndView(&quot;cat&quot;, modelMap);
        }
        @RequestMapping(&quot;/dog&quot;)
        public ModelAndView viewDog(){
            Map modelMap = new HashMap();
            //...
            return new ModelAndView(&quot;dog&quot;, modelMap);
        }
        @RequestMapping(&quot;/cow&quot;)
        public ModelAndView viewCow(){
            Map modelMap = new HashMap();
            //...
            return new ModelAndView(&quot;cow&quot;, modelMap);
        }
    }
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;What’s brilliant here is that we can keep adding new definitions and matching jsps in new folders without ever having to edit the xml.&lt;/p&gt;

&lt;h2 id=&quot;step-2-the-fallback-pattern&quot;&gt;Step 2: The fallback pattern&lt;/h2&gt;

&lt;p&gt;&lt;img src=&quot;/images/2010-11-04-the-ultimate-view/Screenshot.png&quot; alt=&quot;create these files and folders&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Not having to edit tiles.xml anymore is nice, but as the number of definitions grows the number of duplicated jsps with also grow. For example maybe footer and header are identical in nearly every definition.&lt;/p&gt;

&lt;p&gt;Here we looked into writing a custom &lt;a href=&quot;http://tiles.apache.org/framework/tutorial/advanced/attribute-rendering.html&quot;&gt;AttributeRenderer&lt;/a&gt; to give us such a “fallback pattern”.&lt;/p&gt;

&lt;table&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;In the xml the fallbacks are represented within square braces [] where each fallback option follows a pipe character “&lt;/td&gt;
      &lt;td&gt;”. And they are ordered in preference.&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;Now we introduce a “common” folder for jsps, and our example would turn into&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;//--tiles.xml
    &amp;lt;definition name=&quot;REGEXP:(.+)&quot; template=&quot;/WEB-INF/tiles/template.jsp&quot;&amp;gt;
	    &amp;lt;put-attribute name=&quot;meta&quot; value=&quot;/WEB-INF/tiles/[{1}|common]/meta.jsp&quot;/&amp;gt;
	    &amp;lt;put-attribute name=&quot;header&quot; value=&quot;/WEB-INF/tiles/[{1}|common]/header.jsp&quot;/&amp;gt;
	    &amp;lt;put-attribute name=&quot;body&quot; value=&quot;/WEB-INF/tiles/[{1}|common]/body.jsp&quot;/&amp;gt;
	    &amp;lt;put-attribute name=&quot;footer&quot; value=&quot;/WEB-INF/tiles/[{1}|common]/footer.jsp&quot;/&amp;gt;
    &amp;lt;/definition&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The AttributeRenderer, at least a simplified version of it, looks like&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;public class TemplateAttributeRenderer extends org.apache.tiles.renderer.impl.TemplateAttributeRenderer {

        private static final Pattern OPTIONAL_PATTERN
                    = Pattern.compile(Pattern.quote(&quot;[&quot;) + &quot;.+&quot; + Pattern.quote(&quot;]&quot;));

        private static final String OPTION_SEPARATOR = Pattern.quote(&quot;|&quot;);

        @Override
        public void write(Object obj, Attribute attribute, TilesRequestContext request) throws IOException {

            Matcher matcher = null != obj &amp;amp;&amp;amp; obj instanceof String
                    ? OPTIONAL_PATTERN.matcher((String) obj)
                    : null;

            if (null != matcher &amp;amp;&amp;amp; matcher.find()) {
                PrintWriter writer = request.getPrintWriter();
                String match = matcher.group();
                String[] choices = match.substring(1, match.length() - 1).split(OPTION_SEPARATOR);
                for (int i = 0; i &amp;lt; choices.length &amp;amp;&amp;amp; !done; ++i) {
                    String template = ((String) obj).replaceFirst(Pattern.quote(match), choices[i]);
                        try {
                            if (null != applicationContext.getResource(template)) {
                                super.write(template, attribute, request);
                            }
                        } catch (FileNotFoundException ex) {
                            if(ex.getMessage().contains(template)){
                                LOG.trace(ex.getMessage()); // expected outcome. continue loop.

                            }else{
                              throw ex; // comes from an inner templateAttribute.render(..) so throw on
                            }
                        }
                }
            } else {
                super.write(obj, attribute, request);
            }
        }
    }
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;To plug this AttributeRenderer make sure your TilesContainerFactory returns it&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;    public class FinnTilesContainerFactory extends BasicTilesContainerFactory {
        ...
        @Override
        protected AttributeRenderer createTemplateAttributeRenderer(
                BasicRendererFactory rendererFactory,
                TilesApplicationContext applicationContext,
                TilesRequestContextFactory contextFactory,
                TilesContainer container,
                AttributeEvaluatorFactory attributeEvaluatorFactory) {

            TemplateAttributeRenderer templateRenderer = new TemplateAttributeRenderer();
            templateRenderer.setApplicationContext(applicationContext);
            templateRenderer.setRequestContextFactory(contextFactory);
            templateRenderer.setAttributeEvaluatorFactory(attributeEvaluatorFactory);
            return templateRenderer;
        }
        ....
    }
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;As the system developers write new spring controllers creating brand new definitions the front end developers, with this new “Convention over Configuration” Tiles setup, only need to create the  new folder and jsps for fragments they wish to customise. No more xml editing and no duplicate JSPs.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/2010-11-04-the-ultimate-view/information.gif&quot; alt=&quot;information&quot; /&gt;&lt;/p&gt;

&lt;p&gt;For a large company this could help enforce UI standards because they have control over the common/ folder - and they can keep an eye that the overrides, the customisations, never become too outlandish, or too out of line, with the standard look of the website. In turn the front end developers can also look back at the common/ folder to see what the expected default should look like.&lt;/p&gt;

&lt;h2 id=&quot;step-3-definition-includes&quot;&gt;Step 3: Definition includes&lt;/h2&gt;

&lt;p&gt;For a large website these fragments: meta, header, body, and footer; won’t be enough. A real website can quickly be using scores of fragments for columns within the body, banners and advertisements, analytics, seo meta data, css and javascript meta includes, etc, etc.&lt;/p&gt;

&lt;p&gt;And adding another touch of reality is knowing that these different pages: cat, dog, and cow; for any real website would likely have different “actions”. For example view a dog, edit a dog, and search for a dog. Again there’ll be a lot of duplicate jsps.&lt;/p&gt;

&lt;p&gt;By being able to dynamically inject one tiles definitions into another, a la “definition injection”, this was all elegantly handed. Here we’re stepping into the Decorator pattern’s paradigm, needing to decorate our definitions by extending Tiles’ DefinitionFactory. It would also make sense here to take advantage of Tiles’ ViewPreparer, but we weren’t so clear on the ViewPreparer implementation required, and overriding the DefintionFactory is one accepted way to configure Tiles now.&lt;/p&gt;

&lt;p&gt;First of all we change the existing definition names into the form “action.category” making the controller(s) now look like&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;    @Controller
    @RequestMapping(&quot;/cat&quot;)
    public class CatController{
        @RequestMapping(&quot;/cat/view&quot;)
        public ModelAndView viewCat(){ //...
            return new ModelAndView(&quot;view.cat&quot;, modelMap);
        }
        @RequestMapping(&quot;/cat/edit&quot;)
        public ModelAndView editCat(){ //...
            return new ModelAndView(&quot;edit.cat&quot;, modelMap);
        }
        @RequestMapping(&quot;/cat/search&quot;)
        public ModelAndView searchCat(){ //...
            return new ModelAndView(&quot;search.cat&quot;, modelMap);
        }
    }
    @Controller
    @RequestMapping(&quot;/dog&quot;)
    public class DogController{
        @RequestMapping(&quot;/dog/view&quot;)
        public ModelAndView viewDog(){/ /...
            return new ModelAndView(&quot;view.dog&quot;, modelMap);
        }
        @RequestMapping(&quot;/dog/edit&quot;)
        public ModelAndView editDog(){ //...
            return new ModelAndView(&quot;edit.dog&quot;, modelMap);
        }
        @RequestMapping(&quot;/dog/search&quot;)
        public ModelAndView searchDog(){ //...
            return new ModelAndView(&quot;search.dog&quot;, modelMap);
        }
    }
    @Controller
    @RequestMapping(&quot;/cow&quot;)
    public class CowController{
        @RequestMapping(&quot;/cow/view&quot;)
        public ModelAndView viewCow(){ //...
            return new ModelAndView(&quot;view.cow&quot;, modelMap);
        }
        @RequestMapping(&quot;/cow/edit&quot;)
        public ModelAndView editCow(){ //...
            return new ModelAndView(&quot;edit.cow&quot;, modelMap);
        }
        @RequestMapping(&quot;/cow/search&quot;)
        public ModelAndView searchCow(){ //...
            return new ModelAndView(&quot;search.cow&quot;, modelMap);
        }
    }
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;then change the tiles.xml to introduce the definition includes&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;    //-- tiles.xml
    &amp;lt;definition name=&quot;REGEXP:([^.]+)\.([^.]+)&quot; template=&quot;/WEB-INF/tiles/template.jsp&quot;&amp;gt;
        &amp;lt;put-attribute name=&quot;meta&quot; value=&quot;/WEB-INF/tiles/[{2}|{1}|common]/meta.jsp&quot;/&amp;gt;
        &amp;lt;put-attribute name=&quot;header&quot; value=&quot;/WEB-INF/tiles/[{2}|{1}|common]/header.jsp&quot;/&amp;gt;
        &amp;lt;put-attribute name=&quot;body&quot; value=&quot;/WEB-INF/tiles/[{2}|{1}|common]/body.jsp&quot;/&amp;gt;
        &amp;lt;put-attribute name=&quot;footer&quot; value=&quot;/WEB-INF/tiles/[{2}|{1}|common]/footer.jsp&quot;/&amp;gt;

        &amp;lt;!-- definition injection performed by DefinitionInjectingContainerFactory.instantiateDefinitionFactory(..) --&amp;gt;
        &amp;lt;put-list-attribute name=&quot;definition-injection&quot;&amp;gt;
            &amp;lt;add-attribute value=&quot;.category.{2}&quot; type=&quot;string&quot;/&amp;gt;
            &amp;lt;add-attribute value=&quot;.action.{1}.{2}&quot; type=&quot;string&quot;/&amp;gt;
        &amp;lt;/put-list-attribute&amp;gt;
    &amp;lt;/definition&amp;gt;

    //-- tiles-core.xml
    &amp;lt;definition name=&quot;REGEXP:\.action\.view\.([^.]+)&quot;&amp;gt;
        &amp;lt;!-- override attributes --&amp;gt;
        &amp;lt;put-attribute name=&quot;body&quot; value=&quot;/WEB-INF/tiles/[{2}|{1}|view]/view_body.jsp&quot;/&amp;gt;
        &amp;lt;!-- search attributes --&amp;gt;
        &amp;lt;put-attribute name=&quot;view.content&quot; value=&quot;/WEB-INF/tiles/[{2}|{1}|view]/view_content.jsp&quot;/&amp;gt;
        &amp;lt;put-attribute name=&quot;view.statistics&quot; value=&quot;/WEB-INF/tiles/[{2}|{1}|view]/view_statistics.jsp&quot;/&amp;gt;
    &amp;lt;/definition&amp;gt;
    &amp;lt;definition name=&quot;REGEXP:\.action\.edit\.([^.]+)&quot;&amp;gt;
        &amp;lt;!-- override attributes --&amp;gt;
        &amp;lt;put-attribute name=&quot;body&quot; value=&quot;/WEB-INF/tiles/[{2}|{1}|edit]/edit_body.jsp&quot;/&amp;gt;
        &amp;lt;!-- edit attributes --&amp;gt;
        &amp;lt;put-attribute name=&quot;edit.form&quot; value=&quot;/WEB-INF/tiles/[{2}|{1}|view]/edit_form.jsp&quot;/&amp;gt;
        &amp;lt;put-attribute name=&quot;edit.status&quot; value=&quot;/WEB-INF/tiles/[{2}|{1}|view]/edit_status.jsp&quot;/&amp;gt;
    &amp;lt;/definition&amp;gt;
    &amp;lt;definition name=&quot;REGEXP:\.action\.search\.([^.]+)&quot;&amp;gt;
        &amp;lt;!-- override attributes --&amp;gt;
        &amp;lt;put-attribute name=&quot;body&quot; value=&quot;/WEB-INF/tiles/[{2}|{1}|search]/search_body.jsp&quot;/&amp;gt;
        &amp;lt;!-- search attributes --&amp;gt;
        &amp;lt;put-attribute name=&quot;search.form&quot; value=&quot;/WEB-INF/tiles/[{2}|{1}|search]/search_form.jsp&quot;/&amp;gt;
        &amp;lt;put-attribute name=&quot;search.results&quot; value=&quot;/WEB-INF/tiles/[{2}|{1}|search]/search_results.jsp&quot;/&amp;gt;
    &amp;lt;/definition&amp;gt;
    //-- tiles-cat.xml
    &amp;lt;definition name=&quot;REGEXP:\.category\.cat&quot;&amp;gt;
        &amp;lt;!-- cat attributes --&amp;gt;
        &amp;lt;put-attribute name=&quot;cat.extra_information&quot; value=&quot;/WEB-INF/tiles/[{2}|{1}|cat]/cat_extra_information.jsp&quot;/&amp;gt;
        &amp;lt;put-attribute name=&quot;cat.feline_attributes&quot; value=&quot;/WEB-INF/tiles/[{2}|{1}|cat/cat_feline_attributes.jsp&quot;/&amp;gt;
    &amp;lt;/definition&amp;gt;
    //-- tiles-dog.xml
    &amp;lt;definition name=&quot;REGEXP:\.category\.dog&quot;&amp;gt;
        &amp;lt;!-- cat attributes --&amp;gt;
        &amp;lt;put-attribute name=&quot;dog.extra_information&quot; value=&quot;/WEB-INF/tiles/[{2}|{1}|dog]/dog_extra_information.jsp&quot;/&amp;gt;
        &amp;lt;put-attribute name=&quot;dog.k9_attributes&quot; value=&quot;/WEB-INF/tiles/[{2}|{1}|dog/dog_k9_attributes.jsp&quot;/&amp;gt;
    &amp;lt;/definition&amp;gt;
    //-- tiles-cow.xml
    &amp;lt;definition name=&quot;REGEXP:\.category\.cow&quot;&amp;gt;
        &amp;lt;!-- cat attributes --&amp;gt;
        &amp;lt;put-attribute name=&quot;cow.extra_information&quot; value=&quot;/WEB-INF/tiles/[{2}|{1}|cow]/cow_extra_information.jsp&quot;/&amp;gt;
        &amp;lt;put-attribute name=&quot;cow.farm&quot; value=&quot;/WEB-INF/tiles/[{2}|{1}|cow/cow_farm.jsp&quot;/&amp;gt;
    &amp;lt;/definition&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;There’s a lot of configuration here now, but the idea isn’t to avoid configuration altogether but to avoid having to edit the configuration everytime a new jsp or page is created.&lt;/p&gt;

&lt;p&gt;Take note there’s a clear separation now between each category definition and each action definition. For example such separation helps various development roles work in parallel: front end developers can concentrate on categorical designs while system developers often work initially with actions jsps to get them functionally working before passing them off to the front end developers. But of course such separation of concerns will show benefits in many more ways that just this example.&lt;/p&gt;

&lt;p&gt;The following DefinitionsFactory makes this all come together&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;    public class FinnUnresolvingLocaleDefinitionsFactoryImpl extends UnresolvingLocaleDefinitionsFactory {

        private static final String DEF_INJECTION = &quot;definition-injection&quot;;

        public FinnUnresolvingLocaleDefinitionsFactoryImpl() {}

        // this method can return null
        // injected definitions are expected to have &quot;.&quot; prefix
        @Override
        public Definition getDefinition(String name, TilesRequestContext tilesContext) {
            Definition def = null;
            // WEB-INF is a pretty clear indicator it is not a definition
            if(!name.startsWith(&quot;/WEB-INF/&quot;)){
                def = super.getDefinition(name, tilesContext);
                if(null != def){
                    // use a safe copy
                    def = new Definition(def);
                    // explicit injected definitions
                    Attribute defList = def.getLocalAttribute(DEF_INJECTION);
                    if(null != defList &amp;amp;&amp;amp; defList instanceof ListAttribute){
                        List list = (List) defList.getValue();
                        for(Attribute inject : list){
                            injectDefinition((String) inject.getValue(), def, tilesContext);
                        }
                    }
                }
            }
            return def;
        }

        private void injectDefinition(String fromName, Definition to, TilesRequestContext cxt) {
            assert null != fromName : &quot;Definition not found &quot; + fromName;
            assert fromName.startsWith(&quot;.&quot;) : &quot;Injected definitions must have \&quot;.\&quot; prefix: &quot; + fromName;
            Definition from = getDefinition(fromName, cxt);
            if (null != from) {
                if (null != from.getLocalAttributeNames()) {
                    for (String attrName : from.getLocalAttributeNames()) {
                        to.putAttribute(attrName, from.getLocalAttribute(attrName), true);
                    }
                }
            }
        }
    }
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;To plug this DefinitionsFactory in again make sure to return it from your TilesContainerFactory&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;    public class FinnTilesContainerFactory extends BasicTilesContainerFactory {
        ...
        @Override
        protected UnresolvingLocaleDefinitionsFactory instantiateDefinitionsFactory(
                final TilesApplicationContext applicationContext,
                final TilesRequestContextFactory contextFactory,
                final LocaleResolver resolver) {

            return new FinnUnresolvingLocaleDefinitionsFactoryImpl();
        }
        ...
    }
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;when-the-composite-pattern-is-superior&quot;&gt;When the Composite pattern is superior&lt;/h2&gt;

&lt;p&gt;It’s nonsense to think that any one pattern is &lt;em&gt;better&lt;/em&gt;. Both the Composite pattern and the Decorator pattern have their strengths and weaknesses, even after we have progressed the Composite on to being highly dynamic and automated. They do, and achieve, quite different things and hence each should be used where applicable…&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/2010-11-04-the-ultimate-view/lightbulb_on.gif&quot; alt=&quot;lightbulb_on&quot; /&gt;&lt;/p&gt;

&lt;p&gt;The &lt;a href=&quot;http://en.wikipedia.org/wiki/Decorator_pattern&quot;&gt;Decorator pattern&lt;/a&gt; allows front end code and design to be injected as we process. When a decision is known during the request processing the code can immediately build a decorator. The context here only holds all the built decorators. And at the end of the request lifecycle the page is assembled by putting together all these decorators. Decorators can also be built upon each other, or stacked, and this can be useful when the composition of the page is completely loose.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/2010-11-04-the-ultimate-view/lightbulb_on.gif&quot; alt=&quot;lightbulb_on&quot; /&gt;&lt;/p&gt;

&lt;p&gt;The &lt;a href=&quot;http://en.wikipedia.org/wiki/Composite_pattern&quot;&gt;Composite pattern&lt;/a&gt; presumes the page to be a “composite” made up from components, where each component is free to be itself a “composite”. The pattern allows more control of the composite’s heirarchy: as the delegation is top-down as opposed to the Decorator pattern whom’s is bottom-up. The composite pattern works well when the operations you need to perform on each object is limited, that is it isn’t a problem that the operations you may perform on any one object is the lowest common demominator of operations that you can perform on every object.&lt;/p&gt;

&lt;p&gt;When you use jsp includes whether you like it or not you have a page heirarchy. If your jsps typically have a template with fragments you are already working within the composite pattern’s paradigm: the template being the “composite” and the fragments being the components. Each fragment, each JSP, has a context that holds the model map, or variables, in various scopes, and within this context they are self-sufficient. The only operation required upon them by others is inclusion. Applying the composite pattern here means treating each fragment as a self-sufficient object, the only thing that can happen to it is it will be included.&lt;/p&gt;

&lt;p&gt;This shows how we can design to reduce complexity and encourage there to be only one unified context.&lt;/p&gt;

&lt;p&gt;It also shows how we can maintain a top-down control of the page’s heirarchy, something necessary in a MVC design where the control layer wants to hand off a finished model map, ie a fixed context, that the view layer is free to build itself off. In contrast when we choose the Decorator pattern we can run foul of letting code run in parallel to the MVC pattern.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/2010-11-04-the-ultimate-view/patterns-flow.gif&quot; alt=&quot;patterns-flow&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;conclusion&quot;&gt;Conclusion&lt;/h2&gt;

&lt;p&gt;Front end developers, the system developers, and the architects need to work together. With the front end today often centered around loose coupling design: javascript’s lack of type safety, ajax’s separation from the server, and jQuery’s liberation of the dom; it is all too easy to rebel against any form of structure or organisation for fear of being tied into the formalities of the strict-typed java world.&lt;/p&gt;

&lt;p&gt;Neither should the fear of giving up control of one’s craftsmanship mean keeping logic and control in the View layer, this stuff belongs in the Control layer: and this means the system developers need to work harder to get the front end developers collaborating in the control layer. What’s too easily forgotten is that everything has entropy, and the larger the organisation, the larger the codebase, the less you fight the entropy, the quicker you end up with just a pile of spaghetti in your lap.&lt;/p&gt;

&lt;p&gt;The four steps gone through in this article show you how Tiles-2 and the Composite pattern can be ramped up on steroids to give front end and systems developers what they want hopefully in a way that encourages them to work together.&lt;/p&gt;

&lt;h2 id=&quot;references&quot;&gt;References&lt;/h2&gt;

&lt;ol&gt;
  &lt;li&gt;&lt;a href=&quot;http://tiles.apache.org&quot;&gt;Tiles-2&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;http://en.wikipedia.org/wiki/Composite_pattern&quot;&gt;Composite pattern&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;http://en.wikipedia.org/wiki/Decorator_pattern&quot;&gt;Decorator pattern&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;http://static.springsource.org/spring/docs/3.0.x/spring-framework-reference/html/spring-web.html&quot;&gt;Spring Web&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;Integrating &lt;a href=&quot;http://static.springsource.org/spring/docs/3.0.x/spring-framework-reference/html/view.html#view-tiles&quot;&gt;Tiles and Spring&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;Initial discussion regarding Tiles definition delegation (URL not available)&lt;/li&gt;
  &lt;li&gt;Spring &amp;gt;3.0.1 required to work with Tiles-2.2+ :: &lt;a href=&quot;http://jira.springframework.org/browse/SPR-6097&quot;&gt;TilesConfigurer does not …&lt;/a&gt; &lt;a href=&quot;http://jira.springframework.org/browse/SPR-6097&quot;&gt;SPR-6097&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;
</content>
        
        <author>
            <name>mick</name>
            
            <email></email>
            
        </author>
        
    </entry>
    
    <entry>
        <title>Design template</title>
        <link rel="alternate" type="text/html" href="https://tech.finn.no2010/01/01/design-template/"/>
        <updated>2010-01-01T13:37:00+00:00</updated>
        <id>https://tech.finn.no/2010/01/01/design-template</id>
        <content type="html">&lt;h1 id=&quot;headings&quot;&gt;Headings&lt;/h1&gt;

&lt;h1 id=&quot;the-largest-heading-an-h1-tag&quot;&gt;The largest heading (an &amp;lt;h1&amp;gt; tag)&lt;/h1&gt;

&lt;h2 id=&quot;the-second-largest-heading-an-h2-tag&quot;&gt;The second largest heading (an &amp;lt;h2&amp;gt; tag)&lt;/h2&gt;

&lt;h3 id=&quot;the-3rd-largest-heading-an-h3-tag&quot;&gt;The 3rd largest heading (an &amp;lt;h3&amp;gt; tag)&lt;/h3&gt;

&lt;h4 id=&quot;the-4th-largest-heading-an-h4-tag&quot;&gt;The 4th largest heading (an &amp;lt;h4&amp;gt; tag)&lt;/h4&gt;

&lt;h5 id=&quot;the-5th-largest-heading-an-h5-tag&quot;&gt;The 5th largest heading (an &amp;lt;h5&amp;gt; tag)&lt;/h5&gt;

&lt;h6 id=&quot;the-6th-largest-heading-an-h6-tag&quot;&gt;The 6th largest heading (an &amp;lt;h6&amp;gt; tag)&lt;/h6&gt;

&lt;h1 id=&quot;paragraph&quot;&gt;Paragraph&lt;/h1&gt;

&lt;p&gt;Lorem ipsum dolor sit amet, consectetur adipiscing elit. Etiam mattis odio id quam semper, eu scelerisque quam condimentum. Curabitur eu arcu feugiat, vehicula mi ac, mollis lacus. In eu fringilla erat. Vestibulum purus enim, ornare elementum sollicitudin vel, lacinia eu neque. Sed dolor ex, accumsan a egestas et, facilisis et mi. Cras ullamcorper blandit lacus, a posuere arcu tristique ut. Curabitur feugiat placerat aliquam. Fusce sit amet nisl a lectus pellentesque faucibus ut ac massa. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Donec augue urna, lacinia sed maximus pulvinar, tempus eu massa. Vestibulum malesuada nunc ut tortor congue finibus ut vel urna. Phasellus quis arcu quis purus feugiat pulvinar condimentum eget felis. Donec tincidunt euismod auctor. Phasellus congue elementum faucibus. In ex velit, elementum et elementum et, accumsan hendrerit felis. Fusce lobortis bibendum leo eget condimentum.&lt;/p&gt;

&lt;p&gt;Nunc scelerisque, sem quis rutrum scelerisque, lorem mauris scelerisque elit, ut tempus diam tellus at lectus. Donec nec hendrerit sem. Suspendisse eleifend iaculis lorem, nec ultrices nibh convallis sit amet. Ut risus erat, vestibulum vel placerat in, pulvinar nec ex. Ut nec cursus quam, eget pulvinar odio. Interdum et malesuada fames ac ante ipsum primis in faucibus. Pellentesque pretium mauris at ornare accumsan. Donec vel commodo magna. Mauris sed augue tortor. Etiam eget sem quis libero feugiat malesuada. Integer tempor, ligula vel vestibulum sagittis, risus ipsum tempus ipsum, ut maximus eros erat ut augue. Mauris rutrum aliquet est dictum efficitur. Vivamus vestibulum orci massa, ac dignissim mauris lacinia nec. Aliquam volutpat nisl nec ipsum lobortis pellentesque facilisis convallis nulla.&lt;/p&gt;

&lt;p&gt;Vivamus sed interdum nibh, sit amet mollis lorem. Sed ultrices, magna id convallis consectetur, justo nunc commodo justo, at dapibus purus nibh sed mauris. Phasellus vulputate interdum aliquet. Donec a nibh id massa placerat ullamcorper. Vestibulum facilisis leo nulla, vitae porttitor lectus facilisis ac. Nulla facilisi. Aenean sagittis lorem id ante elementum dignissim. Vestibulum eu libero mattis, blandit risus eu, consectetur dolor. Aenean tincidunt nisi lacus, nec sodales est sodales at. Ut efficitur dignissim mauris a ullamcorper. Maecenas eget mi quis diam consequat placerat. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Aenean sed leo at risus feugiat mattis. Integer eget porta leo, at accumsan metus. Aliquam dictum metus sit amet diam tristique pellentesque. Sed facilisis viverra nisi, eget mattis nunc commodo a.&lt;/p&gt;

&lt;h1 id=&quot;blockquotes&quot;&gt;Blockquotes&lt;/h1&gt;

&lt;p&gt;In the words of Abraham Lincoln:&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;Pardon my french&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h1 id=&quot;styling-text&quot;&gt;Styling text&lt;/h1&gt;

&lt;p&gt;&lt;em&gt;This text will be italic&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;This text will be bold&lt;/strong&gt;&lt;/p&gt;

&lt;h1 id=&quot;lists&quot;&gt;Lists&lt;/h1&gt;

&lt;h2 id=&quot;unordered-lists&quot;&gt;Unordered lists&lt;/h2&gt;

&lt;ul&gt;
  &lt;li&gt;Item&lt;/li&gt;
  &lt;li&gt;Item&lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;Item&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;Item&lt;/li&gt;
  &lt;li&gt;Item&lt;/li&gt;
  &lt;li&gt;Item&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;orderd-lists&quot;&gt;Orderd lists&lt;/h2&gt;

&lt;ol&gt;
  &lt;li&gt;Item 1&lt;/li&gt;
  &lt;li&gt;Item 2&lt;/li&gt;
  &lt;li&gt;Item 3&lt;/li&gt;
&lt;/ol&gt;

&lt;h2 id=&quot;nested-lists&quot;&gt;Nested lists&lt;/h2&gt;

&lt;ol&gt;
  &lt;li&gt;Item 1
    &lt;ol&gt;
      &lt;li&gt;A corollary to the above item.&lt;/li&gt;
      &lt;li&gt;Yet another point to consider.&lt;/li&gt;
    &lt;/ol&gt;
  &lt;/li&gt;
  &lt;li&gt;Item 2
    &lt;ul&gt;
      &lt;li&gt;A corollary that does not need to be ordered.&lt;/li&gt;
      &lt;li&gt;This is indented four spaces, because it’s two spaces further than the item above.&lt;/li&gt;
      &lt;li&gt;You might want to consider making a new list.&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;Item 3&lt;/li&gt;
&lt;/ol&gt;

&lt;h1 id=&quot;links&quot;&gt;Links&lt;/h1&gt;

&lt;p&gt;&lt;a href=&quot;http://m.finn.no/&quot;&gt;Visit Finn!&lt;/a&gt;&lt;/p&gt;

&lt;h1 id=&quot;hr&quot;&gt;HR&lt;/h1&gt;

&lt;hr /&gt;

&lt;hr /&gt;

&lt;hr /&gt;

&lt;h1 id=&quot;images&quot;&gt;Images&lt;/h1&gt;

&lt;p&gt;&lt;img src=&quot;https://placekitten.com/g/500/500&quot; alt=&quot;Cat&quot; title=&quot;Cat image&quot; /&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://placekitten.com/g/1000/1000&quot; alt=&quot;Cat&quot; title=&quot;Cat image&quot; /&gt;&lt;/p&gt;

&lt;h1 id=&quot;video&quot;&gt;Video&lt;/h1&gt;

&lt;p&gt;Embed:&lt;/p&gt;

&lt;iframe width=&quot;420&quot; height=&quot;315&quot; src=&quot;//www.youtube.com/embed/dQw4w9WgXcQ&quot; frameborder=&quot;0&quot; allowfullscreen=&quot;&quot;&gt;&lt;/iframe&gt;

&lt;h1 id=&quot;tweet&quot;&gt;Tweet&lt;/h1&gt;

&lt;p&gt;This is a cool tweet:&lt;/p&gt;

&lt;blockquote class=&quot;twitter-tweet&quot; lang=&quot;en&quot;&gt;&lt;p&gt;Practical library for doing feature toggles in Java/JVM: &lt;a href=&quot;https://t.co/XWX2CGxubE&quot;&gt;https://t.co/XWX2CGxubE&lt;/a&gt; by &lt;a href=&quot;https://twitter.com/FINN_tech&quot;&gt;@FINN_tech&lt;/a&gt; (via &lt;a href=&quot;https://twitter.com/rubendw&quot;&gt;@rubendw&lt;/a&gt;)&lt;/p&gt;&amp;mdash; Thomas F. Nicolaisen (@tfnico) &lt;a href=&quot;https://twitter.com/tfnico/status/540059140169928704&quot;&gt;December 3, 2014&lt;/a&gt;&lt;/blockquote&gt;

&lt;h1 id=&quot;tables&quot;&gt;Tables&lt;/h1&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;Tables&lt;/th&gt;
      &lt;th style=&quot;text-align: center&quot;&gt;Are&lt;/th&gt;
      &lt;th style=&quot;text-align: right&quot;&gt;Cool&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;col 3 is&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;right-aligned&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;Yey&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;col 2 is&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;centered&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;Yow&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;zebra stripes&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;are neat&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;Yaw&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;Markdown&lt;/th&gt;
      &lt;th&gt;Less&lt;/th&gt;
      &lt;th&gt;Pretty&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;em&gt;Still&lt;/em&gt;&lt;/td&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;renders&lt;/code&gt;&lt;/td&gt;
      &lt;td&gt;&lt;strong&gt;nicely&lt;/strong&gt;&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;1&lt;/td&gt;
      &lt;td&gt;2&lt;/td&gt;
      &lt;td&gt;3&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;Long list&lt;/p&gt;

&lt;table&gt;
&lt;tr&gt;&lt;th&gt;Domain&lt;/th&gt;&lt;th&gt;Number registered&lt;/th&gt;&lt;th&gt;Percent of total&lt;/th&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;hotmail.com&lt;/td&gt;&lt;td&gt;1106084&lt;/td&gt;&lt;td&gt;28.06%&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;gmail.com&lt;/td&gt;&lt;td&gt;599293&lt;/td&gt;&lt;td&gt;15.20%&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;online.no&lt;/td&gt;&lt;td&gt;444897&lt;/td&gt;&lt;td&gt;11.28%&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;live.no&lt;/td&gt;&lt;td&gt;96052&lt;/td&gt;&lt;td&gt;2.44%&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;yahoo.no&lt;/td&gt;&lt;td&gt;94029&lt;/td&gt;&lt;td&gt;2.39%&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;yahoo.com&lt;/td&gt;&lt;td&gt;75772&lt;/td&gt;&lt;td&gt;1.92%&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;hotmail.no&lt;/td&gt;&lt;td&gt;67602&lt;/td&gt;&lt;td&gt;1.71%&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;c2i.net&lt;/td&gt;&lt;td&gt;67080&lt;/td&gt;&lt;td&gt;1.70%&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;broadpark.no&lt;/td&gt;&lt;td&gt;48232&lt;/td&gt;&lt;td&gt;1.22%&lt;/td&gt;&lt;/tr&gt;
&lt;/table&gt;

&lt;h1 id=&quot;code&quot;&gt;Code&lt;/h1&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-javascript&quot; data-lang=&quot;javascript&quot;&gt;&lt;span class=&quot;kd&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;s&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;JavaScript syntax highlighting&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;nx&quot;&gt;alert&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;s&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-python&quot; data-lang=&quot;python&quot;&gt;&lt;span class=&quot;n&quot;&gt;s&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;Python syntax highlighting&quot;&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;print&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;s&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Code block with line numbers&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-yaml&quot; data-lang=&quot;yaml&quot;&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
2
3
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;na&quot;&gt;yaml&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;true&lt;/span&gt;
&lt;span class=&quot;na&quot;&gt;linenos&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;true&lt;/span&gt;
&lt;span class=&quot;s&quot;&gt;But let's throw in a &amp;lt;b&amp;gt;tag&amp;lt;/b&amp;gt;.&lt;/span&gt;
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-ruby&quot; data-lang=&quot;ruby&quot;&gt;&lt;span class=&quot;nb&quot;&gt;require&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;'redcarpet'&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;markdown&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Redcarpet&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;Hello World!&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;puts&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;markdown&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;to_html&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-java&quot; data-lang=&quot;java&quot;&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;!--&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;core&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;libraries&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;--&amp;gt;&lt;/span&gt;  
&lt;span class=&quot;n&quot;&gt;org&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;apache&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;logging&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;log4j&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;log4j&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;api:&lt;/span&gt;&lt;span class=&quot;mf&quot;&gt;2.0&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;  
&lt;span class=&quot;n&quot;&gt;org&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;apache&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;logging&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;log4j&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;log4j&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;core:&lt;/span&gt;&lt;span class=&quot;mf&quot;&gt;2.0&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;  
&lt;span class=&quot;n&quot;&gt;com&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;lmax&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;disruptor:&lt;/span&gt;&lt;span class=&quot;mf&quot;&gt;3.2&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;  

&lt;span class=&quot;o&quot;&gt;&amp;lt;!--&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;commons&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;logging&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;--&amp;gt;&lt;/span&gt;  
&lt;span class=&quot;n&quot;&gt;org&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;apache&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;logging&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;log4j&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;log4j&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;jcl:&lt;/span&gt;&lt;span class=&quot;mf&quot;&gt;2.0&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;  
&lt;span class=&quot;n&quot;&gt;org&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;apache&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;logging&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;log4j&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;log4j&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;mf&quot;&gt;1.2&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;api:&lt;/span&gt;&lt;span class=&quot;mf&quot;&gt;2.0&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;  

&lt;span class=&quot;o&quot;&gt;&amp;lt;!--&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;slf4j&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;--&amp;gt;&lt;/span&gt;  
&lt;span class=&quot;n&quot;&gt;org&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;slf4j&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;slf4j&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;api:&lt;/span&gt;&lt;span class=&quot;mf&quot;&gt;1.7&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;7&lt;/span&gt;  

&lt;span class=&quot;o&quot;&gt;&amp;lt;!--&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;JUL&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;routed&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;through&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;slf4j&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;--&amp;gt;&lt;/span&gt;  
&lt;span class=&quot;n&quot;&gt;org&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;apache&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;logging&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;log4j&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;log4j&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;slf4j&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;impl:&lt;/span&gt;&lt;span class=&quot;mf&quot;&gt;2.0&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;  

&lt;span class=&quot;o&quot;&gt;&amp;lt;!--&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;override&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;old&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;log4j&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;with&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;an&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;empty&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;jarfile&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;--&amp;gt;&lt;/span&gt;  
&lt;span class=&quot;o&quot;&gt;&amp;lt;!--&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;incase&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;it&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;gets&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;re&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;introduced&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;transitively&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;--&amp;gt;&lt;/span&gt;  
&lt;span class=&quot;nl&quot;&gt;log4j:log4j:&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;empty&lt;/span&gt;  

&lt;span class=&quot;o&quot;&gt;&amp;lt;!--&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;logstash&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;--&amp;gt;&lt;/span&gt;  
&lt;span class=&quot;n&quot;&gt;net&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;logstash&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;log4j2&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;log4j2&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;logstash&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;jsonevent&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;layout:&lt;/span&gt;&lt;span class=&quot;mf&quot;&gt;3.0&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;finn&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;  &lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;h1 id=&quot;emoji&quot;&gt;Emoji&lt;/h1&gt;

&lt;p&gt;:+1:&lt;/p&gt;

&lt;h1 id=&quot;mentions&quot;&gt;Mentions&lt;/h1&gt;

&lt;p&gt;@Andersos&lt;/p&gt;
</content>
        
        <author>
            <name>Andersos</name>
            
            <email></email>
            
        </author>
        
    </entry>
    

</feed>
