Selamat tinggal Redux, MobX, Apollo! Garis antara backend dan frontend rusak! Langkah inovatif dalam evolusi manajer negara.

Salah satu tugas paling sulit dalam mengembangkan aplikasi web dan seluler adalah menyinkronkan data antar perangkat dan melakukan operasi offline. Idealnya, ketika perangkat sedang offline, pelanggan Anda harus dapat terus menggunakan aplikasi Anda tidak hanya untuk mengakses data, tetapi juga untuk membuat dan memodifikasinya. Ketika perangkat kembali online, aplikasi harus menyambungkan kembali ke backend, menyinkronkan data dan menyelesaikan konflik, jika ada. Penanganan yang benar untuk semua kasus ekstrem memerlukan banyak kode yang tidak dibeda-bedakan, bahkan ketika menggunakan cache SDS AppSync AWS pada perangkat dengan mutasi otonom dan sinkronisasi delta.
Amplify DataStore , , , .
Amplify DataStore , , -.
Amplify DataStore ยซ ยป AWS AWS AppSync Amazon DynamoDB.
DataStore Delta Sync GraphQL .
DataStore AWS Amplify Redux, MobX, Apollo, Relay, , :
AWS Amplify Redux, MobX , AWS Amplify -, -, - Apollo Relay.
1. Real time
, , web sockets.
web sockets?
, , real time .
, fullStack serverless AWS Amplify, real time , , , , GraphQL . , , , AWS Amplify Store:
type Job
@model
@auth(
rules: [
{allow: owner, ownerField: "owner", operations: [create, update, delete]},
])
{
id: ID!
position: String!
rate: String!
description: String!
owner: String
}
, , . . , , , vs Redux, MobX, Apollo, Relay.
Redux, MobX, Apollo , . AWS Amplify DataStore
!!!
, .
Serverless โ , , , real time.
2.
, , .
Old school?
fetch axios?
API, Redux, MobX, Apollo, Relay.
!
API, .
, , AWS Amplify DataStore , GraphQL . :
npm run amplify-modelgen
models .
graphql , Flow, TS JavaScript.
3. Offline data & cloud sync
, , .
, , .
Apollo apollo-link-retry . ( ) GraphQL , .
Redux, MobX , , REST . GraphQL vs REST.
AWS Amplify DataStore apollo-link-retry, , .
AWS Amplify , Apollo c loading error , open source , .
c Amplify DataStore:
!
, DataStore . , , .
AWS Amplify: Telegram
Github.
, 5
git clone https://github.com/fullstackserverless/startup.git
cd startup
Install dependencies
yarn
npm install
AWS account
, AWS
5 .
!!!
, 1$
Amplify Command Line Interface (CLI)
AWS Amplify React Native
React Native AWS Amplify
amplify init
:
, , , .
amplify add auth
. . auth ampify/backend/auth
, . default. Enter . Email( SMS ).
amplify push
All resources are updated in the cloud
.
ampify-app
c DataStore โ npx- ampify-app.
npx amplify-app@latest
React Native Cli,
yarn add @aws-amplify/datastore @react-native-community/netinfo @react-native-community/async-storage
React Native> 0.60, iOS:
cd ios && pod install && cd ..
API(App Sync)
, .
, API
amplify add api
GraphQL amplify/backend/api/<datasourcename>/schema.graphql
:
type Job
@model
@auth(
rules: [
{allow: owner, ownerField: "owner", operations: [create, update, delete]},
])
{
id: ID!
position: String!
rate: String!
description: String!
owner: String
}
, DataStore, โ . GraphQL JavaScript, iOS Android , . GraphQL , Automerge AppSync. NPX Amplify CLI.
AWS DataStore, , , Amplify CLI .
,
npm run amplify-modelgen
src/models
API
DataStore API
amplify update api
amplify push
All resources are updated in the cloud
READ
JobsMain src/screens/Jobs/JobsMain.js
Query, , useQuery , Flatlist.
import React, { useEffect, useState } from 'react'
import { FlatList } from 'react-native'
import { Auth } from 'aws-amplify'
import { AppContainer, CardVacancies, Space, Header } from 'react-native-unicorn-uikit'
import { DataStore } from '@aws-amplify/datastore'
import { Job } from '../../models'
import { goBack, onScreen } from '../../constants'
const JobsMain = ({ navigation }) => {
const [data, updateJobs] = useState([])
const fetchJobs = async () => {
const mess = await DataStore.query(Job)
updateJobs(mess)
}
useEffect(() => {
fetchJobs()
const subscription = DataStore.observe(Job).subscribe(() => fetchJobs())
return () => {
subscription.unsubscribe()
}
}, [data])
const _renderItem = ({ item }) => {
const owner = Auth.user.attributes.sub
const check = owner === item.owner
return (
<>
<CardVacancies obj={item} onPress={onScreen(check ? 'JOB_ADD' : 'JOB_DETAIL', navigation, item)} />
<Space height={20} />
</>
)
}
const _keyExtractor = (obj) => obj.id.toString()
return (
<AppContainer onPress={goBack(navigation)} flatlist>
<FlatList
scrollEventThrottle={16}
data={data}
renderItem={_renderItem}
keyExtractor={_keyExtractor}
onEndReachedThreshold={0.5}
ListHeaderComponent={
<Header
onPress={goBack(navigation)}
onPressRight={onScreen('JOB_ADD', navigation)}
iconLeft="angle-dobule-left"
iconRight="plus-a"
/>
}
stickyHeaderIndices={[0]}
/>
</AppContainer>
)
}
export { JobsMain }
JobDetail src/screens/Jobs/JobDetail.js
import React from 'react'
import { Platform } from 'react-native'
import { AppContainer, CardVacancies, Space, Header } from 'react-native-unicorn-uikit'
import { goBack } from '../../constants'
const JobDetail = ({ route, navigation }) => {
return (
<AppContainer>
<Header onPress={goBack(navigation)} iconLeft="angle-dobule-left" />
<CardVacancies obj={route.params} detail />
<Space height={Platform.OS === 'ios' ? 100 : 30} />
</AppContainer>
)
}
export { JobDetail }
CREATE UPDATE DELETE
JobAdd src/screens/Jobs/JobAdd.js, CREATE UPDATE DELETE
import React, { useState, useEffect, useRef } from 'react'
import { AppContainer, Input, Space, Button, Header, ButtonLink } from 'react-native-unicorn-uikit'
import { DataStore } from '@aws-amplify/datastore'
import { Formik } from 'formik'
import * as Yup from 'yup'
import { Job } from '../../models'
import { goBack } from '../../constants'
const JobAdd = ({ route, navigation }) => {
const [loading, setLoading] = useState(false)
const [check, setOwner] = useState(false)
const [error, setError] = useState('')
const [input, setJob] = useState({
id: '',
position: '',
rate: '',
description: ''
})
const formikRef = useRef()
useEffect(() => {
const obj = route.params
if (typeof obj !== 'undefined') {
setOwner(true)
setJob(obj)
const { setFieldValue } = formikRef.current
const { position, rate, description } = obj
setFieldValue('position', position)
setFieldValue('rate', rate)
setFieldValue('description', description)
}
}, [route.params])
const createJob = async (values) => (await DataStore.save(new Job({ ...values }))) && goBack(navigation)()
const updateJob = async ({ position, rate, description }) => {
try {
setLoading(true)
const original = await DataStore.query(Job, input.id)
const update = await DataStore.save(
Job.copyOf(original, (updated) => {
updated.position = position
updated.rate = rate
updated.description = description
})
)
update && goBack(navigation)()
setLoading(false)
} catch (err) {
setError(err)
}
}
const deleteJob = async () => {
try {
setLoading(true)
const job = await DataStore.query(Job, input.id)
const del = await DataStore.delete(job)
del && goBack(navigation)()
setLoading(false)
} catch (err) {
setError(err)
}
}
return (
<AppContainer onPress={goBack(navigation)} loading={loading} error={error}>
<Header onPress={goBack(navigation)} iconLeft="angle-dobule-left" />
<Space height={20} />
<Formik
innerRef={formikRef}
initialValues={input}
onSubmit={(values) => (check ? updateJob(values) : createJob(values))}
validationSchema={Yup.object().shape({
position: Yup.string().min(3).required(),
rate: Yup.string().min(3).required(),
description: Yup.string().min(3).required()
})}
>
{({ values, handleChange, errors, setFieldTouched, touched, isValid, handleSubmit }) => (
<>
<Input
name="position"
value={values.position}
onChangeText={handleChange('position')}
onBlur={() => setFieldTouched('position')}
placeholder="Position"
touched={touched}
errors={errors}
/>
<Input
name="rate"
keyboardType="numeric"
value={`${values.rate}`}
onChangeText={handleChange('rate')}
onBlur={() => setFieldTouched('rate')}
placeholder="Rate"
touched={touched}
errors={errors}
/>
<Input
name="description"
value={values.description}
onChangeText={handleChange('description')}
onBlur={() => setFieldTouched('description')}
placeholder="Description"
touched={touched}
errors={errors}
multiline
numberOfLines={5}
/>
<Space height={40} />
<Button title={check ? 'Update' : 'Create'} disabled={!isValid} onPress={handleSubmit} formik />
{check && (
<>
<Space height={10} />
<ButtonLink title="or" textStyle={{ alignSelf: 'center' }} />
<Space height={15} />
<Button title="DELETE" onPress={deleteJob} cancel />
</>
)}
</>
)}
</Formik>
<Space height={100} />
</AppContainer>
)
}
export { JobAdd }
screens/Jobs/index.js
export * from './JobsMain'
export * from './JobDetail'
export * from './JobAdd'
Jobs StackNavigator
import * as React from 'react'
import { createStackNavigator } from '@react-navigation/stack'
import { enableScreens } from 'react-native-screens' // eslint-disable-line
import { Hello, SignUp, SignIn, ConfirmSignUp, User, Forgot, ForgotPassSubmit } from './screens/Authenticator'
import { JobsMain, JobDetail, JobAdd } from './screens/Jobs'
enableScreens()
const Stack = createStackNavigator()
const AppNavigator = () => {
return (
<Stack.Navigator
screenOptions={{
headerShown: false
}}
initialRouteName="HELLO"
>
<Stack.Screen name="HELLO" component={Hello} />
<Stack.Screen name="SIGN_UP" component={SignUp} />
<Stack.Screen name="SIGN_IN" component={SignIn} />
<Stack.Screen name="FORGOT" component={Forgot} />
<Stack.Screen name="FORGOT_PASSWORD_SUBMIT" component={ForgotPassSubmit} />
<Stack.Screen name="CONFIRM_SIGN_UP" component={ConfirmSignUp} />
<Stack.Screen name="USER" component={User} />
<Stack.Screen name="JOBS_MAIN" component={JobsMain} />
<Stack.Screen name="JOB_DETAIL" component={JobDetail} />
<Stack.Screen name="JOB_ADD" component={JobAdd} />
</Stack.Navigator>
)
}
export default AppNavigator
Jobs
User screens/Authenticator/User/index.js
import React, { useState, useEffect } from 'react'
import { Auth } from 'aws-amplify'
import * as Keychain from 'react-native-keychain'
import { AppContainer, Button } from 'react-native-unicorn-uikit'
import { goHome, onScreen } from '../../../constants'
const User = ({ navigation }) => {
const [loading, setLoading] = useState(false)
const [error, setError] = useState('')
useEffect(() => {
const checkUser = async () => {
await Auth.currentAuthenticatedUser()
}
checkUser()
})
const _onPress = async () => {
setLoading(true)
try {
await Auth.signOut()
await Keychain.resetInternetCredentials('auth')
goHome(navigation)()
} catch (err) {
setError(err.message)
}
}
const _onPressJob = () => onScreen('JOBS_MAIN', navigation)() // JOBS_MAIN
return (
<AppContainer message={error} loading={loading}>
<Button title="Sign Out" onPress={_onPress} />
<Button title="Jobs" onPress={_onPressJob} />
</AppContainer>
)
}
export { User }
Bangun aplikasi dan tes
Selesai
Referensi
https://aws-amplify.imtqy.com
https://learning.oreilly.com/library/view/full-stack-serverless/9781492059882/
https://www.altexsoft.com/blog/engineering/graphql-core-features-architecture-pros-and-cons/
https://engineering.fb.com/core-data/graphql-a-data-query-language/
https://graphql.org/learn