The Agile Way to

Architect Web Apps

Doguhan Uluca

The Agile Way to

Architect Web Apps

Technical Fellow

Excella

Doguhan Uluca

What is a Line-of-Business App?

"set of critical computer applications perceived as vital to running an enterprise"

Source: Wikipedia

5W5H

  • What do we develop?
  • Why/when/where do we develop it?
  • Who develops it?
  • How do we develop it? 
    • Architecture
    • API Design
    • Web App Design
    • Imperative vs Reactive
    • Router-first architecture

What do we develop?

When/where/why do we develop?

Personal

Enterprise

  1. On your own time
  2. Passion-driven
  3. Educational
  4. Likely under engineered
  1. On company time
  2. Requirement-driven
  3. Likely over engineered

When/where/why do we develop?

Personal development

Enterprise development

Line-of-Business Apps

Personal

Enterprise

80% of useful apps

Personal

Enterprise

The Enterprise Niche

extremely complicated or large LOB apps, tools or products 

Line-of-Business Apps

  1. When we start coding, we really never know how much or how little our app will be used

  2. Our apps can quickly become a LOB app

  3. LOB apps are really important to businesses

So

How should we develop?

  1. Must deal with demands of iterative and incremental delivery

  2. Must achieve a constant forward flow of features

  3. Must do all the cloud things

  4. Be scalable

  5. Serve dozens of screen and input types

  6. Must do usability, accessibility

  7. Can't have more than 8 people on the team

  8. Must do backlog grooming

  9. Make sure acceptance criteria is acceptable

However...

  1. CS grads

  2. Career switchers

  3. Reluctant companies

  4. Foot draggers

  5. Lone wolves

  6. Team players

  7. Learners

  8. Stack-Overflowers

Who develops it?

😰

  • Size of our app
  • Reason we are developing the app
  • Skill level of developers
  • Iterative and incremental delivery
  • Constant forward flow of features
  • All the cloud things

How?

The right architecture, tools and patterns/practices.

?

💡

Fundamental Architecture

&

Engineering Fundamentals

High Level Goals of Architecture

  • High Cohesion
  • Low coupling

N-tier Architecture

SOLID

DRY

Modern Web Architecture

  • High cohesion
  • Low coupling
  • Asynchronous
  • Non-blocking I/O
  • Multi-threaded
  • Parallel execution
  • High availability
  • Self-healing
  • Auto-scaling
  • RTO and RPO expressed in minutes/seconds

STATELESS

COMPOSABLE

REACTIVE

  • API DESIGN

  • WEB APP ARCHITECTURE

API design with REST

  • REST is robust, but inflexible
  • API documentation must be enabled manually
    • i.e. Swagger
  • Must optimize every API call
    • Amount of data transferred over the wire matters, a lot
app.get('/users', function (req, res) {
  // return list of users
})

app.get('/user/{id}', function (req, res) {
  // return a single user with id
})

app.put('/user', function (req, res) {
  // update a user
})

REST API example

REST API documentation - Swagger

REST API documentation - Swagger

API design with GraphQL

  • GraphQL makes it possible to iteratively implement
  • Documentation is baked in
  • The consumer decides how much data to pull
  • Inherently stateless
  • Inherently composable
  • Lends itself to functional reactive programming

GraphQL Schema

type Query {
  content(
    source: Audience!
    type: [ContentType!]!
    id: Int
    zoneId: Int
    count: Int = 30
    topNews: Boolean = false
    dayCount: Int = 365
    htmlContent: Boolean = false
  ): [Article]
  ...
}

GraphQL Schema

enum Audience {
  am # Amharic
  bo # Tibetan
  en # European english (RFE/RL)
  enus # US English
  es # Spanish
  fa # Farsi
  ko # Korean
  om # Afaan Oromoo
  prs # Dari
  pus # Pashto
  ru # Russian
  tg # Tigrigna
  ur # Urdu
  vi # Vietnamese
  zhcn # Simplified Chinese
}

enum ContentType {
  Article
  Video
  PhotoGallery
  Clip
}

enum ArticleVideoRelationship {
  SameItem
  MainImage
  EmbededInContent
}

type Photo {
  id: Int
  order: Int
  url: String!
  photoTitle: String
  photoDescription: String
  tiny: String!
  thumb: String!
  hero: String!
}

type PhotoGallery {
  id: Int!
  relType: ArticleVideoRelationship
  photoGalleryTitle: String
  photoGalleryDescription: String
  photo: [Photo]
}

type Name {
  first: String!
  middle: String
  last: String
}

type Author {
  name: Name!
  email: String
  description: String
  id: Int!
}
type Image {
  imageTitle: String
  id: Int!
  type: String
  url: String!
  tiny: String!
  thumb: String!
  hero: String!
}
type Audio {
  audioTitle: String
  audioDescription: String
  id: Int!
  duration: Int
  mime: String
  url: String
  date: String
}

type Video {
  videoTitle: String
  videoDescription: String
  guid: String
  relType: ArticleVideoRelationship
  id: Int!
  width: Int
  height: Int
  duration: Int
  url: String
  thumbnail: String
  thumbnailTiny: String
  thumbnailThumb: String
  thumbnailHero: String
}

type RelatedStory {
  storyTitle: String!
  id: Int!
  pubDate: String!
  type: ContentType
  url: String
  twitter: String
  thumbnailUrl: String
  thumbnailTiny: String
  thumbnailThumb: String
  thumbnailHero: String
}

type Article {
  id: Int!
  site: Int
  zone: Int
  type: ContentType
  pubDate: String!
  lastUpdated: String
  url: String
  twitter: String
  title: String!
  introduction: String!
  content: String
  authors: [Author]
  image: Image
  audio: Audio
  video: Video
  relatedStories: [RelatedStory]
  photoGallery: [PhotoGallery]
  photo: Photo
}

type Zone {
  id: Int!
  site: Int
  hash: String
  type: [ContentType]
  broadcast: Boolean
  name: String!
}

GraphQL example

export const resolvers = <IResolvers>{
  Query: {
    content: async (obj: any, args: IContentQueryParams) => {
      return await getContent(args)
    },
    ...
  },
  Article: {
    type: (obj: any) => {
      return EnumValues.getNameFromValue(ContentType, obj.type)
    },
    authors: (obj: any) => {
      return obj.authors ? wrapAsArray(obj.authors.author) : []
    },
    ...
  },
  ...
}

GraphQL Metrics

  • 11 mobile apps in 14 languages
  • 9 months
  • Kanban
  • Multiple redesigns
  • One major technology shift
  • On time, on budget
  • Deployed on serverless infrastructure
  • Apps work fast in rural areas
  • Two developers

?

💡

  • Exposed to a lot of change by
    • End-users
    • APIs
  • Must be able to evolve the architecture without rewrites
    • Isolation of components is key
    • Custom elements will help

Web App Architecture

Web App Architecture

Angular

app.ts

rootRouter

services

filters

directives

/a: default

/master

/detail

/b/...

/c

childRouter

/d

/e

/f

React

App.js

Presentational

Container

Provider

Router

Component Legend

react-router

react-redux

?

💡

Imperative vs Reactive

  • Must define every step of code execution
  • Easy to introduce state
  • Easy to forget a step
  • Difficult to test

Imperative

  • OOP
  • Functional

Imperative

Reactive

  • Programming with asynchronous data streams
  • Functional
  • Composable
  • Stateless

What is a Data Stream?

  • Event-driven
  • Pub-Sub
  • Asynchronous data stream

Event-Driven Data

Event Source

Event Handler

user clicks

window.alert('Are you sure?')

onClick='confirmDelete()'

Publish-Subscribe Pattern

updated data

fetchDetails()

updateCache()

showToastMessage()

Pub/Sub or Event-based use case

Asynchronous Data Stream

Event Source

mouse clicks

filter(x >= 2)

throttle(250ms)

map(list.length)

<li *ngFor="let i in list | async">

window.alert('Are you sure?')

Reactive or Data Stream use case

  1. Grab a hose
  2. Spray water into the heater
  3. Turn on the faucet for hot water
  4. Send a text to the utility company
  5. Don't forget to undo your steps, when done

Imperative

  1. Turn on/off the faucet for hot water

Reactive

Reactive

products$ = this.http.get<Product[]>(this.productsUrl)
    .pipe(
      tap(data => console.log('getProducts: ', JSON.stringify(data))),
      shareReplay(),
      catchError(this.handleError)
    )

Reactive

productsWithCategory$ = combineLatest(
    this.products$,
    this.productCategoryService.productCategories$
  ).pipe(
    map(([products, categories]) =>
      products.map(
        p =>
          ({
            ...p,
            category: categories.find(c => p.categoryId === c.id).name
          } as Product) // <-- note the type here!
      )
    ),
    shareReplay()
  )

Reactive

Data Composition with RxJS | Deborah Kurata

Thinking Reactively: Most Difficult | Mike Pearson

?

💡

What is Router-First Architecture?

  • enforce high-level thinking
  • ensure consensus on features, before you start coding
  • plan on your codebase/team to grow
  • little engineering overhead

A way to

  1. Develop a roadmap and scope
  2. Design with lazy loading in mind
  3. Implement a walking-skeleton navigation experience
  4. Achieve a stateless, data-driven design
  5. Enforce a decoupled component architecture
  6. Differentiate between user controls and components
  7. Maximize code reuse with ES6/TypeScript

How to implement Router-First?

?

💡

  1. Develop a roadmap and scope
  2. Design with lazy loading in mind
  3. Implement a walking-skeleton navigation experience
  4. Achieve a stateless, data-driven design
  5. Enforce a decoupled component architecture
  6. Differentiate between user controls and components
  7. Maximize code reuse with ES6/TypeScript

How to implement Router-First?

Let's build a weather app

Lemon

Mart

Lemon

Mart

Stakeholders

😎

😄

😄

😄

Site Map

Site Map

Site Map

1. Develop a Roadmap and Scope

 
  • Get high-level architecture right
  • Define the map before getting on the road
  • Capture the vision concretely
  • Bring tools in only when necessary
  • Implement iteratively
  • Drive for perfection only after the fundamentals are in place and agreed upon
  • Document every artifact you create
  1. Develop a roadmap and scope
  2. Design with lazy loading in mind
  3. Implement a walking-skeleton navigation experience
  4. Achieve a stateless, data-driven design
  5. Enforce a decoupled component architecture
  6. Differentiate between user controls and components
  7. Maximize code reuse with ES6/TypeScript

How to implement Router-First?

First-Paint Matters, A Lot

  • 53% of mobile users* abandon if load times > 3 secs
  • Content is consumed mostly on mobile*
  • 70% in the US
  • 90% in China
  • Hybrid client/server-side rendering is hard

*Source: Angular Team, Google Analytics, 2018

Define User Roles

  • manager
  • inventory
  • pos
  • unauthorized

Lazy Loading Tips

  1. Eager-load resources in the background
  2. Optimize your chunk sizes
  3. Watch for amounts of assets and compression
  4. Component level lazy loading will be possible

2. Design with Lazy-Loading in Mind

  • First-paint matters a lot
  • Lazy loading is low hanging fruit
  • Requires user roles to be defined early on
  • Very difficult implement lazy-loading after the fact
  1. Develop a roadmap and scope
  2. Design with lazy loading in mind
  3. Implement a walking-skeleton navigation experience
  4. Achieve a stateless, data-driven design
  5. Enforce a decoupled component architecture
  6. Differentiate between user controls and components
  7. Maximize code reuse with ES6/TypeScript

How to implement Router-First?

  • Gather feedback from users
  • Workout fundamental workflow and integration issues quickly
  • Concrete representation of scope
  • Sets the stage for multiple teams to work in tandem

3. Walking-Skeleton Navigation UX

  1. Develop a roadmap and scope
  2. Design with lazy loading in mind
  3. Implement a walking-skeleton navigation experience
  4. Achieve a stateless, data-driven design
  5. Enforce a decoupled component architecture
  6. Differentiate between user controls and components
  7. Maximize code reuse with ES6/TypeScript

How to implement Router-First?

RxJS/BehaviorSubject as Data Anchors

readonly currentUser$ = new BehaviorSubject<IUser>(
   this.getItem('user') || 
   new User()
)

Using RxJS/BehaviorSubject

<div *ngIf="currentUser$ | async as currentUser">
  <div>{{currentUser.firstName}}</div>
  ...
</div>

4. Be Stateless & Data-Driven

  • Define observable "data anchors"
  • Leverage RxJS features
  • Write functional reactive code
  • Don't store state in components
  • Data across components will be kept in sync
  1. Develop a roadmap and scope
  2. Design with lazy loading in mind
  3. Implement a walking-skeleton navigation experience
  4. Achieve a stateless, data-driven design
  5. Enforce a decoupled component architecture
  6. Differentiate between user controls and components
  7. Maximize code reuse with ES6/TypeScript

How to implement Router-First?

  • @Input bindings
  • @Output bindings
  • Router orchestration

Ways to decouple

Let the router orchestrate

{
  path: 'users',
  component: UserManagementComponent,
  children: [
    { path: '', component: UserTableComponent, outlet: 'master' },
    {
      path: 'user',
      component: ViewUserComponent,
      outlet: 'detail',
      resolve: {
        user: UserResolve,
      },
    },
  ],
  canActivate: [AuthGuard],
  canActivateChild: [AuthGuard],
  data: {
    expectedRole: Role.Manager,
  },
}

5. Be Decoupled

  • Every component should be responsible of loading their own data
  • Allows for composition of components
  • Router enables URL driven composition
  • Don't abuse the router
  • Ok to design for a parent component to contain multiple hard-coded components
  1. Develop a roadmap and scope
  2. Design with lazy loading in mind
  3. Implement a walking-skeleton navigation experience
  4. Achieve a stateless, data-driven design
  5. Enforce a decoupled component architecture
  6. Differentiate between user controls and components
  7. Maximize code reuse with ES6/TypeScript

How to implement Router-First?

  • i.e. Custom Date Input
  • Highly coupled, convoluted, complicated code
  • Using Angular features no one has ever heard of before

What's a User Control?

  • i.e. Form with date inputs
  • Code must be easy to read and understand
  • Stick to Angular basics
  • So code is stable  and easy to maintain

What is a component?

  • User controls encapsulate complicated code
  • User controls can be shipped in libraries
  • User controls can be open sourced
  • Composable components
  • Save time and resources

Reusability

6. User Controls vs Components

  • Wire-framing allows to identify reusable elements early on
  • Keep user interaction code separate from business logic
  • Encapsulate domain specific behavior and share it
  1. Develop a roadmap and scope
  2. Design with lazy loading in mind
  3. Implement a walking-skeleton navigation experience
  4. Achieve a stateless, data-driven design
  5. Enforce a decoupled component architecture
  6. Differentiate between user controls and components
  7. Maximize code reuse with ES6/TypeScript

How to implement Router-First?

DRY

  • Don't Repeat Yourself

Object-Oriented Design

  • Move behavior to classes
  • i.e. Hydration, ToJSON, Calculated Properties
  • Remain stateless and functional
  • Documents shape of data
  • Pass abstractions, not concretions
  • Separate internal data shape from external shape
  • Aim for flat data hierarchy
  • Arrays and simple shapes for common objects are okay
  • i.e. name object, domain-specific object

Interfaces

  • No string literals in code
  • No string literals in code
  • No string literals in code

Use Enums

7. Use ES6 & TypeScript Features

  • Design around abstractions
  • Use classes to reuse context specific behavior
  • Use interfaces to communicate the shape of internal and external data
  • Use enums instead of string literals
  • Leverage Angular Validators and Pipes to reuse logic

?

💡

Router-First Metrics

  • 10 projects

  • 50 developers

  •  1 year of code written

  • 115 of 1000 components written

  • ~25% reduction in package dependencies, variations, and versions 

  • Feedback loop cycle reduced from 20 minutes to 1 minute 

  • ~25% reduction in software bugs reported 

  • ~25% increase in agile velocit y 

Think Router-First

Keep it simple

Master the fundamentals

Be reactive

  • What do we develop?
  • Why/when/where do we develop it?
  • Who develops it?
  • How do we develop it? 
    • Architecture
    • API Design
    • Web App Design
    • Imperative vs Reactive
    • Router-first architecture

5W5H

AngularForEnterprise.com

@duluca

linkedin.com/in/duluca

github.com/duluca

The Agile Way to Architect Web Apps

By Doguhan Uluca

The Agile Way to Architect Web Apps

A router-first approach to SPA design saving development teams significant waste in duplicative work, and re-architecting of the code base; enabling better collaboration and achieving sub-second first meaningful paints in your application

  • 5,466