DataStore - CRUD (Create Read Update Delete)

Auf Wiedersehen Redux, MobX, Apollo! Die Linie zwischen Backend und Frontend ist unterbrochen! Ein innovativer Schritt in der Entwicklung staatlicher Manager.


Datenspeicher


Eine der schwierigsten Aufgaben bei der Entwicklung von Web- und Mobilanwendungen besteht darin, Daten zwischen Geräten zu synchronisieren und Offline-Vorgänge auszuführen. Wenn das Gerät offline ist, sollten Ihre Kunden Ihre Anwendung im Idealfall weiterhin verwenden können, um nicht nur auf Daten zuzugreifen, sondern diese auch zu erstellen und zu ändern. Wenn das Gerät wieder online ist, muss die Anwendung erneut eine Verbindung zum Backend herstellen, die Daten synchronisieren und etwaige Konflikte lösen. Die korrekte Behandlung aller Extremfälle erfordert viel undifferenzierten Code, selbst wenn der AWS AppSync SDK-Cache auf einem Gerät mit autonomen Mutationen und Delta-Synchronisation verwendet wird.


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 }

Erstellen Sie die Anwendung und testen Sie sie


Erledigt


Verweise


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


All Articles