DataStore - CRUD (إنشاء حذف قراءة التحديث)

وداعا Redux ، MobX ، أبولو! الخط الفاصل بين الواجهة الأمامية والخلفية مكسور! خطوة مبتكرة في تطور مديري الدولة.


مخزن البيانات


تتمثل إحدى المهام الأكثر صعوبة في تطوير تطبيقات الويب والجوّال في مزامنة البيانات بين الأجهزة وتنفيذ العمليات في وضع عدم الاتصال. من الناحية المثالية ، عندما يكون الجهاز غير متصل بالإنترنت ، يجب أن يتمكن عملاؤك من الاستمرار في استخدام تطبيقك ليس فقط للوصول إلى البيانات ، ولكن أيضًا لإنشاءه وتعديله. عندما يعود الجهاز عبر الإنترنت ، يجب على التطبيق إعادة الاتصال بالواجهة الخلفية ومزامنة البيانات وحل التعارضات ، إن وجدت. تتطلب المعالجة الصحيحة لجميع الحالات القصوى الكثير من التعليمات البرمجية غير المميزة ، حتى عند استخدام ذاكرة التخزين المؤقت AWS AppSync SDK على جهاز به طفرات مستقلة ومزامنة دلتا.


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 }

بناء التطبيق والاختبار


منجز


المراجع


https://aws-amplify.imtqy.com


https://learning.oreilly.com/library/view/full-stack-serverless/9781492059882/


https://www.altexsoft.com/blog/engineering/graphql-core-features-arch architecture-pros-and-cons/


https://engineering.fb.com/core-data/graphql-a-data-query-language/


https://graphql.org/learn


All Articles