Slide 1

Slide 1 text

Engineering the New Profile Josh Clemm Profile Tech Lead March 2013 | LinkedIn David Fleming Senior Product Manager at Zoomjax San Francisco Bay Area | Software Previous Education Golden Phase, FixDex Silicon Valley Business Academy

Slide 2

Slide 2 text

Let's compare...

Slide 3

Slide 3 text

Really a brand new product Refreshed Look & Feel Simplified Design Surfaced New & Interactive Modules New Data Insights

Slide 4

Slide 4 text

New Features Improved in-line editing experience Modules with In-line searching & pagination

Slide 5

Slide 5 text

The New Profile - Goals ● Represent your entire professional identity ○ Not just your resume ○ Activity, Groups, Following, Connections, Insights about you and your network

Slide 6

Slide 6 text

The New Profile - Goals ● Represent your entire professional identity ○ Not just your resume ○ Activity, Groups, Following, Connections, Insights about you and your network ● Needs to be more interactive ○ Keep users engaged on the page ○ Inline pagination, editing, searching

Slide 7

Slide 7 text

The New Profile - Goals ● Represent your entire professional identity ○ Not just your resume ○ Activity, Groups, Following, Connections, Insights about you and your network ● Needs to be more interactive ○ Keep users engaged on the page ○ Inline pagination, editing, searching ● Needs to be fluid, flexible, fast ○ Progressive rendering ○ Maintain high performance

Slide 8

Slide 8 text

So how did we achieve all that?

Slide 9

Slide 9 text

Using a lot of Technologies

Slide 10

Slide 10 text

And in particular...

Slide 11

Slide 11 text

Let's look at our High Level Frontend Architecture

Slide 12

Slide 12 text

Groups Content Service Connections Content Service Profile Content Service Client/Browser CDN Load Balancer SCDS Fizzy Profile Web App Profile Content Service Profile Web App Connections Content Service Groups Content Service Dust/JS/CSS Server Retrieve Data Models from Mid-tier /profile/view?id=32 top_card.tl, background.tl /profile/topcard /profile/background

Slide 13

Slide 13 text

Groups Content Service Connections Content Service Profile Content Service Client/Browser CDN Load Balancer SCDS Fizzy Profile Web App Profile Content Service Profile Web App Connections Content Service Groups Content Service Dust/JS/CSS Server Retrieve Data Models /profile/view?id=32 top_card.tl, background.tl /profile/topcard /profile/background Our new architecture uses new tech at all layers

Slide 14

Slide 14 text

Groups Content Service Connections Content Service Profile Content Service Client/Browser CDN Load Balancer SCDS Fizzy Profile Web App Profile Content Service Profile Web App Connections Content Service Groups Content Service Dust/JS/CSS Server Retrieve Data Models /profile/view?id=32 top_card.tl, background.tl /profile/topcard /profile/background Let's start at the bottom with Mappers

Slide 15

Slide 15 text

Mappers - JSON endpoints ● Convert data models from mid-tier services into JSON ● Each have an unique endpoint URL ( /profile/positions?id=42 ) Positions Mapper JSON "positions": [{ "position": {"id:{}"}, ]} Biz Profile Model Profile Model Rich Media Rest Model References Model Profile Flex Model

Slide 16

Slide 16 text

Mappers - an example public class PictureMapper extends ProfileParametersAwareMapper { private PictureContentMap pictureContentMap; private static final int ZOOMABLE_DIMENSION = 225; @Override public void doService() { PictureContentModel picCM = getContent(PictureContentModel.class); //declare content needs picCM.criteria().setId(getVieweeId()); //supply any input params assemble(); //invoke framework to retrieve declared content if (isResolvedWithoutErrors(picCM)) { //all went well, create new content map to hold output (JavaBean-like objects) pictureContentMap = ContentMap.proxyNew(PictureContentMap.class); Integer pictureWidth = picCM.getPictureWidth(); pictureContentMap.setIsZoomable(pictureWidth >= ZOOMABLE_DIMENSION); if(pictureWidth != null && pictureWidth > 0) { pictureContentMap.setWidth(pictureWidth); pictureContentMap.setHeight(picCM.getPictureHeight()); } pictureContentMap.setPictureID(picCM.getPictureID()); } } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29

Slide 17

Slide 17 text

Mappers - an example public class PictureMapper extends ProfileParametersAwareMapper { private PictureContentMap pictureContentMap; private static final int ZOOMABLE_DIMENSION = 225; @Override public void doService() { PictureContentModel picCM = getContent(PictureContentModel.class); //declare content needs picCM.criteria().setId(getVieweeId()); //supply any input params assemble(); //invoke framework to retrieve declared content if (isResolvedWithoutErrors(picCM)) { //all went well, create new content map to hold output (JavaBean-like objects) pictureContentMap = ContentMap.proxyNew(PictureContentMap.class); Integer pictureWidth = picCM.getPictureWidth(); pictureContentMap.setIsZoomable(pictureWidth >= ZOOMABLE_DIMENSION); if(pictureWidth != null && pictureWidth > 0) { pictureContentMap.setWidth(pictureWidth); pictureContentMap.setHeight(picCM.getPictureHeight()); } pictureContentMap.setPictureID(picCM.getPictureID()); } } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 Declare the data you need Set the data you want coming back as JSON

Slide 18

Slide 18 text

Mappers - features ● Modular ○ A single mapper can retrieve data for a section ■ Positions, Educations, Groups, etc. ● Reusable & Combinable ○ Mappers can be used more than once ■ Positions Mapper is used for Positions section and the positions part of Top Card ○ You can Aggregate Mappers under a common root element and a new URL endpoint

Slide 19

Slide 19 text

Profile's Many Mappers ● Each section on Profile has either a single Mapper or Aggregated Mapper (like Top Card) for its data

Slide 20

Slide 20 text

Profile's Many Mappers ● Each section on Profile has either a single Mapper or Aggregated Mapper (like Top Card) for its data Profile Web App Summary Mapper Positions Mapper Educations Mapper Picture Mapper Top Card Mapper "Summary": { "summary": "I'm an experienced..."} JSON JSON "TopCard": { "positions": {}, "educations":{}, "picture":{}} Groups Mapper Connections Mapper Patents Mapper "Connections": { "profiles": [ ...]} JSON

Slide 21

Slide 21 text

So we have these Mappers that return JSON for each section. Who calls each one?

Slide 22

Slide 22 text

Groups Content Service Connections Content Service Profile Content Service Client/Browser CDN Load Balancer SCDS Fizzy Profile Web App Profile Content Service Profile Web App Connections Content Service Groups Content Service Dust/JS/CSS Server Retrieve Data Models /profile/view?id=32 top_card.tl, background.tl /profile/topcard /profile/background Fizzy - the UI aggregator

Slide 23

Slide 23 text

Fizzy ● Fizzy is an UI aggregator in 2 parts: ○ Fizzy Server fetches the content you want ○ Fizzy Client renders it when ready ● Your base page defines its structure and which UI components it needs (called "embeds") *Fizzy Server is an Apache Traffic Server Plugin, Fizzy Client is a JS library

Slide 24

Slide 24 text

Profile's Embeds Top Card Embed Activity Embed Embed Embed Embed Embed Yet another embed

Slide 25

Slide 25 text

Profile's Embeds in code ...
<script type=“embed" fs-id=“background" fs-uri=“/profile/background"/> ... <script type=“embed" fs-id=“connections" fs-uri=“/profile/connections"/> </div> <div id=“insights"> <script type=“embed" fs-id=“people_you_may_know" fs-uri=“/profile/pymk"/> <script type=“embed" fs-id=“strength_meter" fs-uri=“/profile/strength"/> ... <script type=“embed" fs-id=“in_common" fs-uri=“/profile/incommon"/> </div> </div> </body> </html> 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19

Slide 26

Slide 26 text

Profile's Embeds in code ...
<script type=“embed" fs-id=“background" fs-uri=“/profile/background"/> ... <script type=“embed" fs-id=“connections" fs-uri=“/profile/connections"/> </div> <div id=“insights"> <script type=“embed" fs-id=“people_you_may_know" fs-uri=“/profile/pymk"/> <script type=“embed" fs-id=“strength_meter" fs-uri=“/profile/strength"/> ... <script type=“embed" fs-id=“in_common" fs-uri=“/profile/incommon"/> </div> </div> </body> </html> 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 Each embed specifies a Mapper endpoint that Fizzy will fetch

Slide 27

Slide 27 text

Fizzy fetches data and sends it to the browser. What's rendering the actual markup?

Slide 28

Slide 28 text

Groups Content Service Connections Content Service Profile Content Service Client/Browser CDN Load Balancer SCDS Fizzy Profile Web App Profile Content Service Profile Web App Connections Content Service Groups Content Service Dust/JS/CSS Server Retrieve Data Models /profile/view?id=32 top_card.tl, background.tl /profile/topcard /profile/background You guessed it... Dust client templates

Slide 29

Slide 29 text

{Dust} client templates ● LinkedIn's latest and greatest rendering layer ● Logic-less client template language ● Profile page made up of many* templates ● Templates + JSON = full markup! *over 400 actually

Slide 30

Slide 30 text

{Dust} - what it looks like

{name}

{headline}
{#info}
{location} | {industry}
{/info}
{ "name": "Frank Stallone", "headline": "Actor and Less Famous Brother", "info":{ "location": "Hollywood", "industry": "Entertainment" } }

Slide 31

Slide 31 text

{Dust} - why it's cool for profile ● Cached markup in CDNs and browsers ○ Members browse many profiles in a row ● Very DRY and reusable ○ Improved development speed ○ Templates within templates within templates... ■ date range, degree badge, formatted summary field ● Super easy to refresh a section on the page ○ Re-render and replace ○ Useful in pagination, inline searching, and inline editing

Slide 32

Slide 32 text

{Dust} on profile {>"tl/apps/profile/v2/embed/company_logo"/}

{title_highlight|s} {?selfView} {i18n_Edit} {/selfView}

{?companyName} {>"tl/apps/profile/v2/embed/company_link" track_param="prof-exp"/} {/companyName}
{>"tl/apps/profile/v2/partial/daterange"/} {! Location !} {@pre.fmt key="fmt_location" type="geo.region" value="{location}" render="false"/} {?fmt_location}{fmt_location} {:else} {?locationName} {locationName} {/locationName} {/fmt_location} {>"tl/apps/profile/v2/partial/summary_field" _summary=summary/} {>"tl/apps/profile/v2/partial/associated_content" trkCodePrefix="exp"/}

Slide 33

Slide 33 text

{Dust} on profile {>"tl/apps/profile/v2/embed/company_logo"/}

{title_highlight|s} {?selfView} {i18n_Edit} {/selfView}

{?companyName} {>"tl/apps/profile/v2/embed/company_link" track_param="prof-exp"/} {/companyName}
{>"tl/apps/profile/v2/partial/daterange"/} {! Location !} {@pre.fmt key="fmt_location" type="geo.region" value="{location}" render="false"/} {?fmt_location}{fmt_location} {:else} {?locationName} {locationName} {/locationName} {/fmt_location} {>"tl/apps/profile/v2/partial/summary_field" _summary=summary/} {>"tl/apps/profile/v2/partial/associated_content" trkCodePrefix="exp"/} Partial templates like this are used on almost all the background sections

Slide 34

Slide 34 text

We have our Mappers, Fizzy, and Dust templates... Let's bring it all together.

Slide 35

Slide 35 text

Feature: Inline Editing ● Dust + Mappers make this easy ● Dust templates for view and edit ● Mappers return just the data we need to refresh "View" template "Edit" template Summary Section

Slide 36

Slide 36 text

Feature: Inline Editing ● Dust + Mappers make this easy ● Dust templates for view and edit ● Mappers return just the data we need to refresh "View" template "Edit" template Summary Section When edit link is clicked, render "edit" template

Slide 37

Slide 37 text

Feature: Inline Editing ● Dust + Mappers make this easy ● Dust templates for view and edit ● Mappers return just the data we need to refresh "View" template "Edit" template Summary Section On submit, our endpoint sends back JSON, and we re-render

Slide 38

Slide 38 text

Inline editing - Example Either the "view" or "edit" template is shown at a time

Slide 39

Slide 39 text

Inline editing sounds easy... ● Issue #1: Profile data is highly coupled with different sections. ○ Adding/editing a position needs to be reflected on Top Card... ○ Deleting a project needs to also be removed from any associated position... ● Solution: since we have mappers for each section, we know which to re-fetch upon a save and refresh the respective templates

Slide 40

Slide 40 text

What about pagination, search? ● Same idea but easier (not coupled with other sections) ● Mappers can take URL offset params Switch out a partial template containing just list of profiles

Slide 41

Slide 41 text

Let's talk performance

Slide 42

Slide 42 text

High Performance Page ● Profile is the most trafficked page on LinkedIn ● Profile fetches 48+ different types of content on each page load ○ This content results in 250+ total downstream calls

Slide 43

Slide 43 text

High Performance Page ● Profile is the most trafficked page on LinkedIn ● Profile fetches 48+ different types of content on each page load ○ This content results in 250+ total downstream calls Bottom Line: We need to be fast, but need to consider downstream fanout

Slide 44

Slide 44 text

High Performance - Parallel Requests Fizzy Profile App Profile App Profile App Profile App Profile App Profile App Profile App Profile App Profile App Profile App In the beginning, Profile had 15 embeds with 15 different endpoints which is great for speed...

Slide 45

Slide 45 text

High Performance - Parallel Requests Fizzy Profile App Profile App Profile App Profile App Profile App Profile App Profile App Profile App Profile App Profile App Content Service Content Service Content Service Content Service Content Service Content Service Content Service Content Service Content Service Content Service That's a lot of downstream calls, often requesting the same data too. Content Service Content Service

Slide 46

Slide 46 text

High Performance - Parallel Requests Fizzy Profile App Profile App Profile App Profile App Profile App Profile App Profile App Profile App Profile App Profile App Content Service Content Service Content Service Content Service Content Service Content Service Content Service Content Service Content Service Content Service Content Service Content Service and those calls call more and more... until...

Slide 47

Slide 47 text

High Performance - Parallel Requests Fizzy Profile App Profile App Profile App Profile App Profile App Profile App Profile App Profile App Profile App Profile App Content Service Content Service Content Service Content Service Content Service Content Service Content Service Content Service Content Service Content Service Content Service Content Service aaaaand the site's down

Slide 48

Slide 48 text

High Performance - Parallel Requests ● Tradeoff: Speed vs Scalability ● 15 parallel calls will be fast but with Profile's load will take down site

Slide 49

Slide 49 text

High Performance - Parallel Requests ● Tradeoff: Speed vs Scalability ● 15 parallel calls will be fast but with Profile's load will take down site Batched Endpoints to the rescue

Slide 50

Slide 50 text

Batching Calls to Mappers Originally, we had separate endpoints (URIs) for each embed 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 ...
<script type=“embed" fs-id=“background" fs-uri=“/profile/background"/> ... <script type=“embed" fs-id=“connections" fs-uri=“/profile/connections"/> </div> <div id=“insights"> <script type=“embed" fs-id=“peeps_you_may_know" fs-uri=“/profile/pymk"/> <script type=“embed" fs-id=“strength_meter" fs-uri=“/profile/strength"/> ... <script type=“embed" fs-id=“in_common" fs-uri=“/profile/incommon"/> </div> </div> </body> </html>

Slide 51

Slide 51 text

Batching Calls to Mappers We now tell framework to batch these two endpoints (Fizzy knows how to deliver the right data to the right embed) ...
<script type=“embed" fs-id=“background" fs-uri=“/profile/mappers?a=topcard,background"/> ... <script type=“embed" fs-id=“connections" fs-uri=“/profile/connections"/> </div> <div id=“insights"> <script type=“embed" fs-id=“peeps_you_may_know" fs-uri=“/profile/pymk"/> <script type=“embed" fs-id=“strength_meter" fs-uri=“/profile/strength"/> ... <script type=“embed" fs-id=“in_common" fs-uri=“/profile/incommon"/> </div> </div> </body> </html> 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19

Slide 52

Slide 52 text

Batching Calls in Profile ● Today, we use between 3-5 parallel requests to Profile ● It's a good balance of speed vs. scalability ● Batching requests that need the same data has the added benefit of less downstream calls

Slide 53

Slide 53 text

Progressive Rendering With parallel fetch, Profile modules render when ready for improved perceived performance

Slide 54

Slide 54 text

Progressive Rendering With parallel fetch, Profile modules render when ready for improved perceived performance

Slide 55

Slide 55 text

How else to improve page load times?

Slide 56

Slide 56 text

Profiles can be long... Embed Embed Embed Embed Embed Embed Embed Profile Page

Slide 57

Slide 57 text

Optimize for Above the Fold Embed Embed Embed Embed Embed Embed Embed Profile Page The fold No need to render No need to render or fetch

Slide 58

Slide 58 text

Optimize for Above the Fold ● The New Profile renders above the fold as fast as we can ○ Deferring everything at the bottom ○ Reduces the static content we need to initially download

Slide 59

Slide 59 text

Optimize for Above the Fold ● The New Profile renders above the fold as fast as we can ○ Deferring everything at the bottom ○ Reduces the static content we need to initially download Sorry guys, we’ll get to you later

Slide 60

Slide 60 text

Optimize for Above the Fold ● The New Profile defers fetching the modules at the bottom of the page ○ Improves server side assembly times ○ Lowers payload and improves network time ○ Improves client rendering times

Slide 61

Slide 61 text

Optimize for Above the Fold ● The New Profile defers fetching the modules at the bottom of the page ○ Improves server side assembly times ○ Lowers payload and improves network time ○ Improves client rendering times Go fetch!

Slide 62

Slide 62 text

So we covered the product, technologies, and some features... let's revisit our goals.

Slide 63

Slide 63 text

Revisiting the New Profile Goals ● Needs to surface your entire professional identity ○ Easily expose new data endpoints ● Needs to be more interactive ○ Inline editing, searching ● Needs to be ○ Fluid ○ Flexible ○ Fast

Slide 64

Slide 64 text

Revisiting the New Profile Goals ● Needs to surface your entire professional identity ○ Easily expose new data endpoints (Mappers) ● Needs to be more interactive ○ Inline editing, searching (Dust + Mappers) ● Needs to be ○ Fluid (Fizzy progressive rendering) ○ Flexible (Fizzy deferred rendering) ○ Fast (Fizzy parallel calls + defer fetch, batching Mappers, Dust template caching)

Slide 65

Slide 65 text

Takeaways ● Mappers for each module makes sense ○ You can combine and reuse with ease ○ A must if refreshing part of page ● When structuring your page, start with many embeds ○ You can control number of requests, when embeds are rendered, and when to fetch data ● Many partial templates are a good thing ○ Allows you finer control over what to re-render ○ Improves developer speed

Slide 66

Slide 66 text

Takeaways cont. ● Make these technologies work for you ○ Our Mappers are structured to guarantee minimal downstream calls ○ Take advantage of decoupling rendering layer with server side endpoints ■ Engineers can build endpoints ■ Web devs can start the templates with mocked JSON data

Slide 67

Slide 67 text

Thanks! Josh Clemm linkedin.com/in/joshclemm