Doguhan Uluca
Technical Fellow
Excella
Source: Wikipedia
Personal
Enterprise
Line-of-Business Apps
Personal
Enterprise
Personal
Enterprise
extremely complicated or large LOB apps, tools or products
Line-of-Business Apps
When we start coding, we really never know how much or how little our app will be used
Our apps can quickly become a LOB app
LOB apps are really important to businesses
Must deal with demands of iterative and incremental delivery
Must achieve a constant forward flow of features
Must do all the cloud things
Be scalable
Serve dozens of screen and input types
Must do usability, accessibility
Can't have more than 8 people on the team
Must do backlog grooming
Make sure acceptance criteria is acceptable
CS grads
Career switchers
Reluctant companies
Foot draggers
Lone wolves
Team players
Learners
Stack-Overflowers
😰
The right architecture, tools and patterns/practices.
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
})
type Query {
content(
source: Audience!
type: [ContentType!]!
id: Int
zoneId: Int
count: Int = 30
topNews: Boolean = false
dayCount: Int = 365
htmlContent: Boolean = false
): [Article]
...
}
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!
}
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) : []
},
...
},
...
}
app.ts
rootRouter
services
filters
directives
/a: default
/master
/detail
/b/...
/c
childRouter
/d
/e
/f
App.js
Presentational
Container
Provider
Router
Component Legend
react-router
react-redux
Event Source
Event Handler
user clicks
window.alert('Are you sure?')
onClick='confirmDelete()'
updated data
fetchDetails()
updateCache()
showToastMessage()
Event Source
mouse clicks
filter(x >= 2)
throttle(250ms)
map(list.length)
<li *ngFor="let i in list | async">
window.alert('Are you sure?')
products$ = this.http.get<Product[]>(this.productsUrl)
.pipe(
tap(data => console.log('getProducts: ', JSON.stringify(data))),
shareReplay(),
catchError(this.handleError)
)
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()
)
Data Composition with RxJS | Deborah Kurata
Thinking Reactively: Most Difficult | Mike Pearson
A way to
😎
😄
😄
😄
*Source: Angular Team, Google Analytics, 2018
readonly currentUser$ = new BehaviorSubject<IUser>(
this.getItem('user') ||
new User()
)
<div *ngIf="currentUser$ | async as currentUser">
<div>{{currentUser.firstName}}</div>
...
</div>
{
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,
},
}
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
AngularForEnterprise.com
@duluca
linkedin.com/in/duluca
github.com/duluca