SlideShare una empresa de Scribd logo
1 de 92
Descargar para leer sin conexión
StoreDataLoadingMadeEasy-ish
Mike Nakhimovich - NY Times
We ❤Making Apps
We ❤Open Source
Why?Open Source Makes Life Easier
Networking is easier:
HTTPURLCONNECTION
Volley
Retrofit/ Okhttp
Storage is Easier
SharedPreferences
Firebase
Realm
SqlBrite/SqlDelight
Parsing is Easier
JSONObject
Jackson
Gson
Moshi
Fetching, Persisting &
Parsing datahas become
easy
What's NOT Easy?
DATALOADINGEveryone does itdifferently
Whatis DataLoading?
TheActofgetting
datafroman external
systemtoauser'sscreen
Loading is
complicated
Rotation Handling isa special
snowflake
NewYorkTimes builtStoreto
simplifydataloading
github.com/NYTimes/store
OUR GOALS
Datashould survive
configuration changes
agnostic ofwhere itcomes from or
howitis needed
Activitiesand
presenters should
stopretaining MBs
ofdata
Offline as configuration
Cachingas standard,
notan exception
API should be
simple enough for
an internto use,yet
robust enough
for everydataload.
Howdowework
with DataatNY
Times?
80% case:
Need data, don'tcare
iffresh or cached
Need fresh data
background
updates &
pull to refresh
Requests needto be
async & reactive
Datais dependenton
each other,
DataLoading should
betoo
Performance is
importantNewcalls should hook into in flight
responses
Parsing should be
done efficientlyand
minimallyParse onceand cachethe resultfor future calls
Let's Use Repository
PatternBy creating reactiveand persistent
DataStores
Repository Pattern
Separatethe logicthatretrievesthe
dataand maps ittothe [view] model
fromthe [view] logicthatacts onthe
model.
The repositorymediates betweenthe
datalayerandthe [view] layer ofthe
application.
WhyRepository?
Maximizetheamountof
codethatcan betested
withautomation by
isolatingthe datalayer
Why
Repository?
Makes iteasierto have
multiple devsworking on
same feature
Why
Repository?
Datasource from many
locationswillbe centrally
managedwith consistent
access rulesand logic
Our Implementation
https://github.com/NYTimes/Store
WhatisaStore?
Aclassthatmanagesthe
fetching, parsing,and
storage ofaspecific data
model
Tell a Store:
Whatto fetch
Tell a Store:
Whatto fetch
Whereto cache
Tell a Store:
Whatto fetch
Whereto cache
Howto parse
Tell a Store:
Whatto fetch
Whereto cache
Howto parse
The Store handles flow
Storesare Observable
Observable<T> get( V key);
Observable<T> fetch(V key)
Observable<T> stream()
void clear(V key)
Let's see howstores
helped usachieve
our goalsthrougha
PizzaExample
Getis for Handling our 80% Use Case
public final class PizzaActivity {
Store<Pizza, String> pizzaStore;
void onCreate() {
store.get("cheese")
.subscribe(pizza ->.show(pizza));
}
}
Howdoes itflow?
On Configuration Change
You onlyneedto Save/Restoreyour !to get
your cached "
public final class PizzaActivity {
void onRecreate(String topping) {
store.get(topping)
.subscribe(pizza ->.show(pizza));
}
}
Please do notserializeaPizza
Itwould beverymessy
Whatwe gain?:Fragments/Presenters don'tneedto be
retained
NoTransactionTooLargeExceptions
Onlykeys needto be Parcelable
Efficiencyis Important:
for(i=0;i<20;i++){
store.get(topping)
.subscribe(pizza -> getView()
.setData(pizza));
}
ManyconcurrentGetrequestswillstillonly
hitnetwork once
Fetching NewDatapublic class PizzaPresenter {
Store<Pizza, String> store;
void onPTR() {
store.fetch("cheese")
.subscribe(pizza ->
getView().setData(pizza));
}
}
Howdoes Store routethe request?
Fetchalsothrottles multiple requestsand
broadcastresult
Stream listens for events
public class PizzaBar {
Store<Pizza, String> store;
void showPizzaDone() {
store.stream()
.subscribe(pizza ->
getView().showSnackBar(pizza));
}
}
How do We build a
Store?
By implementing interfaces
Interfaces
Fetcher<Raw, Key>{
Observable<Raw> fetch(Key key);
}
Persister<Raw, Key>{}
Observable<Raw> read(Key key);
Observable<Boolean> write(Key key, Raw raw);
}
Parser<Raw, Parsed> extends Func1<Raw, Parsed>{
Parsed call(Raw raw);
}
Fetcher defines how a Store will get new data
Fetcher<Pizza,String> pizzaFetcher =
new Fetcher<Pizza, String>() {
public Observable<Pizza> fetch(String topping) {
return pizzaSource.fetch(topping);
};
Becoming Observable
Fetcher<Pizza,String> pizzaFetcher =
topping ->
Observable.fromCallable(() -> client.fetch(topping));
Parsers helpwith
Fetchersthatdon't
returnViewModels
Some ParsersTransform
Parser<Pizza, PizzaBox> boxParser = new Parser<>() {
public PizzaBox call(Pizza pizza) {
return new PizzaBox(pizza);
}
};
Others read Streams
Parser<BufferedSource, Pizza> parser = source -> {
InputStreamReader reader =
new InputStreamReader(source.inputStream());
return gson.fromJson(reader, Pizza.class);
}
MiddleWare is forthe commonJSON cases
Parser<BufferedSource, Pizza> parser
= GsonParserFactory.createSourceParser(gson,Pizza.class)
Parser<Reader, Pizza> parser
= GsonParserFactory.createReaderParser(gson,Pizza.class)
Parser<String, Pizza> parser
= GsonParserFactory.createStringParser(gson,Pizza.class)
'com.nytimes.android:middleware:CurrentVersion'
'com.nytimes.android:middleware:jackson:CurrentVersion'
'com.nytimes.android:middleware-moshi:CurrentVersion'
Data FlowwithaParser
Adding Offline with Persisters
File System Record Persister
FileSystemRecordPersister.create(
fileSystem,key -> "pizza"+key, 1, TimeUnit.DAYS);
File System is KISS storage
interface FileSystem {
BufferedSource read(String var) throws FileNotFoundException;
void write(String path, BufferedSource source) throws IOException;
void delete(String path) throws IOException;
}
compile 'com.nytimes.android:filesystem:CurrentVersion'
Don'tlike our persisters?
No Problem! You
can implement your
own
Persister interfaces
Persister<Raw, Key> {
Observable<Raw> read(Key key);
Observable<Boolean> write(Key key, Raw raw);
}
Clearable<Key> {
void clear(Key key);
}
RecordProvider<Key> {
RecordState getRecordState( Key key);
DataFlowwithaPersister
We have our components!
Let's buildand openastore
MultiParser
List<Parser> parsers=new ArrayList<>();
parsers.add(boxParser);
parsers.add(GsonParserFactory.createSourceParser(gson, Pizza.class));
Store Builder
List<Parser> parsers=new ArrayList<>();
parsers.add(boxParser);
parsers.add(GsonParserFactory.createSourceParser(gson, Pizza.class));
StoreBuilder<String,BufferedSource,Pizza> parsedWithKey()
Add Our Parser
List<Parser> parsers=new ArrayList<>();
parsers.add(boxParser);
parsers.add(GsonParserFactory.createSourceParser(gson, Pizza.class));
StoreBuilder<String,BufferedSource,Pizza> parsedWithKey()
.fetcher(topping -> pizzaSource.fetch(topping))
NowOur Persister
List<Parser> parsers=new ArrayList<>();
parsers.add(boxParser);
parsers.add(GsonParserFactory.createSourceParser(gson, Pizza.class));
StoreBuilder<String,BufferedSource,Pizza> parsedWithKey()
.fetcher(topping -> pizzaSource.fetch(topping))
.persister( FileSystemRecordPersister.create( fileSystem,
key -> "prefix"+key, 1, TimeUnit.DAYS))
And Our Parsers
List<Parser> parsers=new ArrayList<>();
parsers.add(boxParser);
parsers.add(GsonParserFactory.createSourceParser(gson, Pizza.class));
StoreBuilder<String,BufferedSource,Pizza> parsedWithKey()
.fetcher(topping -> pizzaSource.fetch(topping))
.persister( FileSystemRecordPersister.create( fileSystem,
key -> "prefix"+key, 1, TimeUnit.DAYS))
.parsers(parsers)
Timeto OpenaStore
List<Parser> parsers=new ArrayList<>();
parsers.add(boxParser);
parsers.add(GsonParserFactory.createSourceParser(gson, Pizza.class));
Store<Pizza, String> pizzaStore =
StoreBuilder<String,BufferedSource,Pizza> parsedWithKey()
.fetcher(topping -> pizzaSource.fetch(topping))
.persister( FileSystemRecordPersister.create( fileSystem,
key -> "prefix"+key, 1, TimeUnit.DAYS))
.parsers(parsers)
.open();
Configuring caches
Configuring MemoryPolicy
StoreBuilder
.<String, BufferedSource, Pizza>parsedWithKey()
.fetcher(topping -> pizzaSource.fetch(topping))
.memoryPolicy(MemoryPolicy
.builder()
.setMemorySize(10)
.setExpireAfter(24)
.setExpireAfterTimeUnit(HOURS)
.build())
.open()
Refresh On Stale - BackFillingthe Cache
Store<Pizza, String> pizzaStore = StoreBuilder
.<String, BufferedSource, Pizza>parsedWithKey()
.fetcher(topping -> pizzaSource.fetch(topping))
.parsers(parsers)
.persister(persister)
.refreshOnStale()
.open();
Network Before Stales Policy
Store<Pizza, String> pizzaStore = StoreBuilder
.<String, BufferedSource, Pizza>parsedWithKey()
.fetcher(topping -> pizzaSource.fetch(topping))
.parsers(parsers)
.persister(persister)
.networkBeforeStale()
.open();
Stores inthewild
Intern Project: BestSellers List
public interface Api {
@GET
Observable<BufferedSource>
getBooks(@Path("category") String category);
@Provides
@Singleton
Store<Books, BarCode> provideBooks(FileSystem fileSystem,
Gson gson, Api api) {
return StoreBuilder.<BarCode, BufferedSource, Books>
parsedWithKey()
.fetcher(category -> api.getBooks(category))
.persister(SourcePersisterFactory.create(fileSystem))
.parser(GsonParserFactory.createSourceParser(gson, Books.class))
.open();
}
public class BookActivity{
...
onCreate(...){
bookStore
.get(category)
.subscribe();
}
Howdo books getupdated?
public class BackgroundUpdater{
...
bookStore
.fresh(category)
.subscribe();
}
DataAvailablewhen screen needs it
UI calls get, background services call fresh
DependentCalls
Simple case Using RxJavaOperators
MapStore resulttoanother Store Result
public Observable<SectionFront> getSectionFront(@NonNull final String name) {
return feedStore.get()
.map(latestFeed -> latestFeed.getSectionOrBlog(name))
.flatMap(section -> sfStore.get(SectionFrontId.of(section)));
}
More Complicated Example
Video Store Returns singlevideos,
PlaylistStore returns playlist,
Howdowe combine?
Relationships by overriding
Get/Fetch
Step1: CreateVideo Store
Store<Video, Long> provideVideoStore(VideoFetcher fetcher,
Gson gson) {
return StoreBuilder.<Long, BufferedSource, Video>parsedWithKey()
.fetcher(fetcher)
.parser(GsonParserFactory
.createSourceParser(gson, Video.class))
.open();
}
Step 2: Create PlaylistStore
public class VideoPlaylistStore extends RealStore<Playlist, Long> {
final Store<CherryVideoEntity, Long> videoStore;
public VideoPlaylistStore(@NonNull PlaylistFetcher fetcher,
@NonNull Store<CherryVideoEntity, Long> videoStore,
@NonNull Gson gson) {
super(fetcher, NoopPersister.create(),
GsonParserFactory.createSourceParser(gson, Playlist.class));
this.videoStore = videoStore;
}
Step 3: Override store.get()
public class VideoPlaylistStore extends RealStore<Playlist, Long> {
final Store<CherryVideoEntity, Long> videoStore;
@Override
public Observable<Playlist> get(Long playlistId) {
return super.get(playlistId)
.flatMap(playlist ->
from(playlist.videos())
.concatMap(video -> videoStore.get(video.id()))
.toList()
.map(videos ->
builder().from(playlist)
.videos(videos)
.build()));
}
Listening for changes
Step1: Subscribeto Store, Filterwhatyou need
sectionFrontStore.subscribeUpdates()
.observeOn(scheduler)
.subscribeOn(Schedulers.io())
.filter(this::sectionIsInView)
.subscribe(this::handleSectionChange);
Step2: Kick offafresh requesttothatstore
public Observable<SectionFront> refreshAll(final String sectionName) {
return feedStore.get()
.flatMapIterable(this::updatedSections)
.map(section->sectionFrontStore.fetch(section))
latestFeed -> fetch(latestFeed.changedSections));
}
Bonus: Howwe made Store
RxJavaforAPIand FunctionalNiceties
public Observable<Parsed> get(@Nonnull final Key key) {
return Observable.concat(
cache(key),
fetch(key)
).take(1);
}
public Observable<Parsed> getRefreshing(@Nonnull final Key key) {
return get(key)
.compose(repeatOnClear(refreshSubject, key));
}
GuavaCaches For MemoryCache
memCache = CacheBuilder
.newBuilder()
.maximumSize(maxSize())
.expireAfterWrite(expireAfter(),timeUnit())
.build();
In FlightThrottlerToo
inflighter.get(key, new Callable<Observable<Parsed>>() {
public Observable<Parsed> call() {
return response(key);
}
})
compile 'com.nytimes.android:cache:CurrentVersion'
Why GuavaCaches?
Theywillblockacrossthreads saving precious MBs ofdownloads
when making same requests in parallel
FileSystem:
Builtusing OKIOtoallowstreaming
from Networkto disk & diskto parser
On Fresh InstallNYTimesappsuccessfullydownloadsand caches
dozens ofmbs ofdata
Would love
contributions &
feedback!thanks for listening

Más contenido relacionado

La actualidad más candente

Appengine Java Night #2b
Appengine Java Night #2bAppengine Java Night #2b
Appengine Java Night #2b
Shinichi Ogawa
 
Appengine Java Night #2a
Appengine Java Night #2aAppengine Java Night #2a
Appengine Java Night #2a
Shinichi Ogawa
 
Enterprise Guice 20090217 Bejug
Enterprise Guice 20090217 BejugEnterprise Guice 20090217 Bejug
Enterprise Guice 20090217 Bejug
robbiev
 

La actualidad más candente (20)

Contract-driven development with OpenAPI 3 and Vert.x | DevNation Tech Talk
Contract-driven development with OpenAPI 3 and Vert.x | DevNation Tech TalkContract-driven development with OpenAPI 3 and Vert.x | DevNation Tech Talk
Contract-driven development with OpenAPI 3 and Vert.x | DevNation Tech Talk
 
End to end todo list app with NestJs - Angular - Redux & Redux Saga
End to end todo list app with NestJs - Angular - Redux & Redux SagaEnd to end todo list app with NestJs - Angular - Redux & Redux Saga
End to end todo list app with NestJs - Angular - Redux & Redux Saga
 
Appengine Java Night #2b
Appengine Java Night #2bAppengine Java Night #2b
Appengine Java Night #2b
 
Appengine Java Night #2a
Appengine Java Night #2aAppengine Java Night #2a
Appengine Java Night #2a
 
Intro to Retrofit 2 and RxJava2
Intro to Retrofit 2 and RxJava2Intro to Retrofit 2 and RxJava2
Intro to Retrofit 2 and RxJava2
 
#ajn3.lt.marblejenka
#ajn3.lt.marblejenka#ajn3.lt.marblejenka
#ajn3.lt.marblejenka
 
[Srijan Wednesday Webinar] Rails 5: What's in It for Me?
[Srijan Wednesday Webinar] Rails 5: What's in It for Me?[Srijan Wednesday Webinar] Rails 5: What's in It for Me?
[Srijan Wednesday Webinar] Rails 5: What's in It for Me?
 
Retrofit
RetrofitRetrofit
Retrofit
 
Java Libraries You Can’t Afford to Miss
Java Libraries You Can’t Afford to MissJava Libraries You Can’t Afford to Miss
Java Libraries You Can’t Afford to Miss
 
FullStack Reativo com Spring WebFlux + Angular
FullStack Reativo com Spring WebFlux + AngularFullStack Reativo com Spring WebFlux + Angular
FullStack Reativo com Spring WebFlux + Angular
 
Android getting started
Android getting startedAndroid getting started
Android getting started
 
Durable functions
Durable functionsDurable functions
Durable functions
 
iOS Keychain by 흰, 민디
iOS Keychain by 흰, 민디iOS Keychain by 흰, 민디
iOS Keychain by 흰, 민디
 
Enterprise Guice 20090217 Bejug
Enterprise Guice 20090217 BejugEnterprise Guice 20090217 Bejug
Enterprise Guice 20090217 Bejug
 
Android httpclient php_mysql
Android httpclient php_mysqlAndroid httpclient php_mysql
Android httpclient php_mysql
 
第一次用Parse就深入淺出
第一次用Parse就深入淺出第一次用Parse就深入淺出
第一次用Parse就深入淺出
 
Parse Advanced
Parse AdvancedParse Advanced
Parse Advanced
 
Dependency rejection and TDD without Mocks
Dependency rejection and TDD without MocksDependency rejection and TDD without Mocks
Dependency rejection and TDD without Mocks
 
Using Java to interact with Firebase in Android
Using Java to interact with Firebase in AndroidUsing Java to interact with Firebase in Android
Using Java to interact with Firebase in Android
 
Android Libs - Retrofit
Android Libs - RetrofitAndroid Libs - Retrofit
Android Libs - Retrofit
 

Similar a Data Loading Made Easy with Mike Nakhimovich DroidCon Italy 2017

Ft10 de smet
Ft10 de smetFt10 de smet
Ft10 de smet
nkaluva
 
Working effectively with legacy code
Working effectively with legacy codeWorking effectively with legacy code
Working effectively with legacy code
ShriKant Vashishtha
 
Ingesting streaming data for analysis in apache ignite (stream sets theme)
Ingesting streaming data for analysis in apache ignite (stream sets theme)Ingesting streaming data for analysis in apache ignite (stream sets theme)
Ingesting streaming data for analysis in apache ignite (stream sets theme)
Tom Diederich
 

Similar a Data Loading Made Easy with Mike Nakhimovich DroidCon Italy 2017 (20)

Apache Calcite Tutorial - BOSS 21
Apache Calcite Tutorial - BOSS 21Apache Calcite Tutorial - BOSS 21
Apache Calcite Tutorial - BOSS 21
 
Adopting F# at SBTech
Adopting F# at SBTechAdopting F# at SBTech
Adopting F# at SBTech
 
하이퍼커넥트 데이터 팀이 데이터 증가에 대처해온 기록
하이퍼커넥트 데이터 팀이 데이터 증가에 대처해온 기록하이퍼커넥트 데이터 팀이 데이터 증가에 대처해온 기록
하이퍼커넥트 데이터 팀이 데이터 증가에 대처해온 기록
 
twMVC#46 一探 C# 11 與 .NET 7 的神奇
twMVC#46 一探 C# 11 與 .NET 7 的神奇twMVC#46 一探 C# 11 與 .NET 7 的神奇
twMVC#46 一探 C# 11 與 .NET 7 的神奇
 
Construindo APIs de forma produtiva com Spring Boot, Spring Data e Spring MVC
Construindo APIs de forma produtiva com Spring Boot, Spring Data e Spring MVCConstruindo APIs de forma produtiva com Spring Boot, Spring Data e Spring MVC
Construindo APIs de forma produtiva com Spring Boot, Spring Data e Spring MVC
 
Change tracking
Change trackingChange tracking
Change tracking
 
Getting Started with Real-Time Analytics
Getting Started with Real-Time AnalyticsGetting Started with Real-Time Analytics
Getting Started with Real-Time Analytics
 
Rethinking Syncing at AltConf 2019
Rethinking Syncing at AltConf 2019Rethinking Syncing at AltConf 2019
Rethinking Syncing at AltConf 2019
 
Wcf data services
Wcf data servicesWcf data services
Wcf data services
 
Bringing JAMStack to the Enterprise
Bringing JAMStack to the EnterpriseBringing JAMStack to the Enterprise
Bringing JAMStack to the Enterprise
 
Ft10 de smet
Ft10 de smetFt10 de smet
Ft10 de smet
 
Working effectively with legacy code
Working effectively with legacy codeWorking effectively with legacy code
Working effectively with legacy code
 
meetstore5.pdf
meetstore5.pdfmeetstore5.pdf
meetstore5.pdf
 
Bootiful Development with Spring Boot and React - UberConf 2018
Bootiful Development with Spring Boot and React - UberConf 2018Bootiful Development with Spring Boot and React - UberConf 2018
Bootiful Development with Spring Boot and React - UberConf 2018
 
Bootiful Development with Spring Boot and React - RWX 2017
Bootiful Development with Spring Boot and React - RWX 2017Bootiful Development with Spring Boot and React - RWX 2017
Bootiful Development with Spring Boot and React - RWX 2017
 
Ingesting streaming data for analysis in apache ignite (stream sets theme)
Ingesting streaming data for analysis in apache ignite (stream sets theme)Ingesting streaming data for analysis in apache ignite (stream sets theme)
Ingesting streaming data for analysis in apache ignite (stream sets theme)
 
2015 09-02 - transaction log prototype
2015 09-02 - transaction log prototype2015 09-02 - transaction log prototype
2015 09-02 - transaction log prototype
 
Marvel of Annotation Preprocessing in Java by Alexey Buzdin
Marvel of Annotation Preprocessing in Java by Alexey BuzdinMarvel of Annotation Preprocessing in Java by Alexey Buzdin
Marvel of Annotation Preprocessing in Java by Alexey Buzdin
 
Building a Real-Time Feature Store at iFood
Building a Real-Time Feature Store at iFoodBuilding a Real-Time Feature Store at iFood
Building a Real-Time Feature Store at iFood
 
Spicy javascript: Create your first Chrome extension for web analytics QA
Spicy javascript: Create your first Chrome extension for web analytics QASpicy javascript: Create your first Chrome extension for web analytics QA
Spicy javascript: Create your first Chrome extension for web analytics QA
 

Último

Finding Java's Hidden Performance Traps @ DevoxxUK 2024
Finding Java's Hidden Performance Traps @ DevoxxUK 2024Finding Java's Hidden Performance Traps @ DevoxxUK 2024
Finding Java's Hidden Performance Traps @ DevoxxUK 2024
Victor Rentea
 
+971581248768>> SAFE AND ORIGINAL ABORTION PILLS FOR SALE IN DUBAI AND ABUDHA...
+971581248768>> SAFE AND ORIGINAL ABORTION PILLS FOR SALE IN DUBAI AND ABUDHA...+971581248768>> SAFE AND ORIGINAL ABORTION PILLS FOR SALE IN DUBAI AND ABUDHA...
+971581248768>> SAFE AND ORIGINAL ABORTION PILLS FOR SALE IN DUBAI AND ABUDHA...
?#DUbAI#??##{{(☎️+971_581248768%)**%*]'#abortion pills for sale in dubai@
 
Cloud Frontiers: A Deep Dive into Serverless Spatial Data and FME
Cloud Frontiers:  A Deep Dive into Serverless Spatial Data and FMECloud Frontiers:  A Deep Dive into Serverless Spatial Data and FME
Cloud Frontiers: A Deep Dive into Serverless Spatial Data and FME
Safe Software
 

Último (20)

Corporate and higher education May webinar.pptx
Corporate and higher education May webinar.pptxCorporate and higher education May webinar.pptx
Corporate and higher education May webinar.pptx
 
Connector Corner: Accelerate revenue generation using UiPath API-centric busi...
Connector Corner: Accelerate revenue generation using UiPath API-centric busi...Connector Corner: Accelerate revenue generation using UiPath API-centric busi...
Connector Corner: Accelerate revenue generation using UiPath API-centric busi...
 
Finding Java's Hidden Performance Traps @ DevoxxUK 2024
Finding Java's Hidden Performance Traps @ DevoxxUK 2024Finding Java's Hidden Performance Traps @ DevoxxUK 2024
Finding Java's Hidden Performance Traps @ DevoxxUK 2024
 
Apidays New York 2024 - Scaling API-first by Ian Reasor and Radu Cotescu, Adobe
Apidays New York 2024 - Scaling API-first by Ian Reasor and Radu Cotescu, AdobeApidays New York 2024 - Scaling API-first by Ian Reasor and Radu Cotescu, Adobe
Apidays New York 2024 - Scaling API-first by Ian Reasor and Radu Cotescu, Adobe
 
MS Copilot expands with MS Graph connectors
MS Copilot expands with MS Graph connectorsMS Copilot expands with MS Graph connectors
MS Copilot expands with MS Graph connectors
 
Apidays New York 2024 - The Good, the Bad and the Governed by David O'Neill, ...
Apidays New York 2024 - The Good, the Bad and the Governed by David O'Neill, ...Apidays New York 2024 - The Good, the Bad and the Governed by David O'Neill, ...
Apidays New York 2024 - The Good, the Bad and the Governed by David O'Neill, ...
 
Axa Assurance Maroc - Insurer Innovation Award 2024
Axa Assurance Maroc - Insurer Innovation Award 2024Axa Assurance Maroc - Insurer Innovation Award 2024
Axa Assurance Maroc - Insurer Innovation Award 2024
 
[BuildWithAI] Introduction to Gemini.pdf
[BuildWithAI] Introduction to Gemini.pdf[BuildWithAI] Introduction to Gemini.pdf
[BuildWithAI] Introduction to Gemini.pdf
 
Apidays New York 2024 - The value of a flexible API Management solution for O...
Apidays New York 2024 - The value of a flexible API Management solution for O...Apidays New York 2024 - The value of a flexible API Management solution for O...
Apidays New York 2024 - The value of a flexible API Management solution for O...
 
Ransomware_Q4_2023. The report. [EN].pdf
Ransomware_Q4_2023. The report. [EN].pdfRansomware_Q4_2023. The report. [EN].pdf
Ransomware_Q4_2023. The report. [EN].pdf
 
+971581248768>> SAFE AND ORIGINAL ABORTION PILLS FOR SALE IN DUBAI AND ABUDHA...
+971581248768>> SAFE AND ORIGINAL ABORTION PILLS FOR SALE IN DUBAI AND ABUDHA...+971581248768>> SAFE AND ORIGINAL ABORTION PILLS FOR SALE IN DUBAI AND ABUDHA...
+971581248768>> SAFE AND ORIGINAL ABORTION PILLS FOR SALE IN DUBAI AND ABUDHA...
 
Apidays New York 2024 - APIs in 2030: The Risk of Technological Sleepwalk by ...
Apidays New York 2024 - APIs in 2030: The Risk of Technological Sleepwalk by ...Apidays New York 2024 - APIs in 2030: The Risk of Technological Sleepwalk by ...
Apidays New York 2024 - APIs in 2030: The Risk of Technological Sleepwalk by ...
 
ProductAnonymous-April2024-WinProductDiscovery-MelissaKlemke
ProductAnonymous-April2024-WinProductDiscovery-MelissaKlemkeProductAnonymous-April2024-WinProductDiscovery-MelissaKlemke
ProductAnonymous-April2024-WinProductDiscovery-MelissaKlemke
 
AWS Community Day CPH - Three problems of Terraform
AWS Community Day CPH - Three problems of TerraformAWS Community Day CPH - Three problems of Terraform
AWS Community Day CPH - Three problems of Terraform
 
FWD Group - Insurer Innovation Award 2024
FWD Group - Insurer Innovation Award 2024FWD Group - Insurer Innovation Award 2024
FWD Group - Insurer Innovation Award 2024
 
Spring Boot vs Quarkus the ultimate battle - DevoxxUK
Spring Boot vs Quarkus the ultimate battle - DevoxxUKSpring Boot vs Quarkus the ultimate battle - DevoxxUK
Spring Boot vs Quarkus the ultimate battle - DevoxxUK
 
EMPOWERMENT TECHNOLOGY GRADE 11 QUARTER 2 REVIEWER
EMPOWERMENT TECHNOLOGY GRADE 11 QUARTER 2 REVIEWEREMPOWERMENT TECHNOLOGY GRADE 11 QUARTER 2 REVIEWER
EMPOWERMENT TECHNOLOGY GRADE 11 QUARTER 2 REVIEWER
 
Polkadot JAM Slides - Token2049 - By Dr. Gavin Wood
Polkadot JAM Slides - Token2049 - By Dr. Gavin WoodPolkadot JAM Slides - Token2049 - By Dr. Gavin Wood
Polkadot JAM Slides - Token2049 - By Dr. Gavin Wood
 
AXA XL - Insurer Innovation Award Americas 2024
AXA XL - Insurer Innovation Award Americas 2024AXA XL - Insurer Innovation Award Americas 2024
AXA XL - Insurer Innovation Award Americas 2024
 
Cloud Frontiers: A Deep Dive into Serverless Spatial Data and FME
Cloud Frontiers:  A Deep Dive into Serverless Spatial Data and FMECloud Frontiers:  A Deep Dive into Serverless Spatial Data and FME
Cloud Frontiers: A Deep Dive into Serverless Spatial Data and FME
 

Data Loading Made Easy with Mike Nakhimovich DroidCon Italy 2017