background

With the increasing development and popularity of cloud native technology, multi-language microservice architecture (Python, Go, PHP….) Multiple systems communicate with each other through RPC. Once the interface needs to be upgraded, coordinating the release and upgrade of the server side, the change of IDL file warehouse, and the change release of the client side become a very troublesome thing. We need a convenient and transparent specification to coordinate the ends, and a more automated tool to coordinate. Take the mainstream GRPC as an example to discuss, where is the proto and code in the end? How do you automate version control?


Plan 1: Put in the respective code repository

As shown below, all proto files and client code that the project depends on as well as its own are stored directly in the protobuf/ directory without external tools.

It is obvious that this scheme has the following advantages and disadvantages.

Disadvantages:

  • 1 all proTO dependent on ️ project is stored under the code warehouse of their respective projects, so all dependent proTO needs to be manually “needed” from other business groups and then put into the directory of Protobuf /, which is extremely troublesome for manual intervention. Too much reliance on copy can become a burden.

  • 2 there is no way to maintain the control and update of ️ version and the communication cost is too high.

  • 3 The reusability of ️ code is poor.

Advantages:

  • Simple to use, each project’s PROto dependencies at a glance. No need to look here or there.

Plan 2: Independent Proto warehouse

Each project has its own proto repository. Dependencies are pulled from the Proto repository (including the service itself).

However, there are some obvious disadvantages to this approach:

Disadvantages:

  • 1️ each service needs to pay attention to its own service and proTO warehouse at the same time. Proto and client code should be defined and generated in proTO warehouse first during development, so switch between service warehouse and ProTO warehouse is needed during development.

  • 2️ retail if the service depends too much on, it may also cross business groups. For example, the figure below shows that after crossing groups, the principal of the business group may need to open multiple PROTO warehouse permissions. You need to rely on them one by one.


  • 3️ every time there is a new service, it is troublesome to apply for a PROto warehouse.

Advantages:

  • 1️ each service has its own PROTO warehouse, which is convenient for version maintenance and upgrade.

  • 2️ dependence can be drawn on demand.

Plan three: centralized warehouse

Manage the PROTO repository by business group dimension, so that if multiple dependencies are dependent on a business group, only one PROTO repository needs to be pulled. At the same time, each time a new service is created, the proOTO warehouse of the respective business group only needs to add its own service, and there is no need to apply for a proTO warehouse of its own service separately.


Advantages:

  • 1️ Dependence on proTO of multiple services only needs to rely on central warehouse.

  • 2️ new services do not need to apply for PROTO warehouse separately, but only need to be added in the PROTO warehouse of their respective business groups.

Disadvantages:

  • 1️ retail service development or need to pay attention to two warehouses, need to cut.

  • 2️ version management of proTO of various services cannot be carried out independently under each central warehouse.

  • 3️ one may only rely on the PROto of a CERTAIN SVC, while introducing some other unnecessary ones.

Solution 4: Mirror repository + Git Branch

In order to solve some of the pain points of the above solutions, we combined the advantages of each solution with the way of mirror warehouse and Git Branch based on the central warehouse.Description:

  • 1 Master readme of central warehouse of ️ retail maintains the corresponding relationship between branches and services, as shown in the figure below.

  • 2️ discount is mainly used to obtain the branch name of the next branch when new SVC is added (due to the limitation of go.mod branch version management, it can only be named V2 and V3), and at the same time it is convenient for people to identify which module SVC they depend on. Common represents the set branch of all SVC proTos. A description is added to this REame during cicD each time a new service is added.

  • 3 The readme branches of ️ central warehouse maintain their own versions of the situation.


  • 4️ can be used according to their own needs to rely on the corresponding branch version.

  • 5️ Python CLEint version and GO version are one-to-one corresponding.


  • 6️ each SVC only needs to rely on a. Gitlab. yml file to realize. Specific Gitlab CICD jobs are as follows:



    .push_tmpl: &push_proto

      script:

        - echo "push test"

        - echo $CI_PROJECT_NAME

        - |

          userMail=$GITLAB_USER_EMAIL

          git config --global user.email "$GITLAB_USER_EMAIL"

          userName=${userMail%@*}

          echo "$userMail"

          git config --global user.name "$userName"

          git clone -v https://xxxxx/proto-center.git

          cd proto-center

    if [ `grep -c $CI_PROJECT_NAME README.md` -eq '0' ]; then

            echo "- $CI_PROJECT_NAME-->v$(sed -n '$p' README.md | awk -F "-->v" '{print $2+1}' | head)" >>README.md

            export branchName=$(sed -n '$p' README.md | awk -F "-->" '{print $2}')

            echo $branchName

            git add . && git commit -m "add $CI_PROJECT_NAME "

            git push  https://xxxxxxxx/proto-center.git master

            git checkout -b $branchName

    mkdir -p $CI_PROJECT_NAME && cp -r .. /protobuf/* $CI_PROJECT_NAME/

            go mod init xxxxxxxx/proto-center/$branchName

            go mod tidy

            git add .

            git commit -m "add  $CI_PROJECT_NAME proto"

            git push --set-upstream https:/xxxxxxxx/proto-center.git $branchName

            git checkout v2

    mkdir -p $CI_PROJECT_NAME && cp -r .. /protobuf/* $CI_PROJECT_NAME/

            git add .

            git commit -m "add  $CI_PROJECT_NAME proto"

            git push  https://xxxxxxxx/proto-center.git v2

          else

            export branchName=$(grep $CI_PROJECT_NAME README.md  | awk -F "-->" '{print $2}' | head)

            git checkout $branchName

            rm -rf $CI_PROJECT_NAME/

            mkdir -p $CI_PROJECT_NAME

    cp -r .. /protobuf/* $CI_PROJECT_NAME/ && git add .

            git commit -m "update  $CI_PROJECT_NAME proto"

            git push https://xxxxxxxx/proto-center.git $branchName;

            git checkout v2

            rm -rf $CI_PROJECT_NAME/

            mkdir -p $CI_PROJECT_NAME

    cp -r .. /protobuf/* $CI_PROJECT_NAME/ && git add .

            git commit -m "update  $CI_PROJECT_NAME proto"

            git push https://xxxxxxxx/proto-center.git v2;

          fi

    Copy the code
  • 7 The jobs of Gitlab AND CICD of ️ central warehouse are as follows:

    .buld_tmpl: &tag_proto



      script:

        - echo "tag proto "

        - |

          userMail=$GITLAB_USER_EMAIL

          userName=${userMail%@*}

          echo "$userMail"

          git config --global user.email $userMail

          git config --global user.name $userName

          branch=$CI_BUILD_REF_NAME

          isnottag="false"

          git describe --tag || isnottag="true"

          git clone -b $branch -v https://xxxxx/proto-center.git

          cd proto-center

          userMail=$( git log --pretty=format:%ae ${CI_COMMIT_SHA} -1)

          userName=${userMail%@*}

          echo "$userMail"

          git config --global user.email $userMail

          git config --global user.name $userName

          version=""

    if [ $isnottag = "true" ]; then

            echo $isnottag

    Version = $branch. 0.1

            echo $version

            git tag $version

            git push  https://xxxxx/proto-center.git --tags

            echo "- $version" >> README.md

            git add .

            git commit -m "add $version"

            git push  https://xxxxx/proto-center.git $branch

          else

            echo $isnottag

            version_ref=$(git describe --tags | awk -F "-" '{print $1}' | head)

            echo $version_ref

            version=`echo $version_ref | awk -F. -v OFS=. 'NF==1{print ++$NF}; NF>1{if(length($NF+1)>length($NF))$(NF-1)++; $NF=sprintf("%0*d", length($NF), ($NF+1)%(10^length($NF))); print}'`

            echo $version

            git tag $version

            git push  https://xxxxx/proto-center.git  --tags

            echo "- $version" >> README.md

            git add .

            git commit -m "add $version"

            git push  https://xxxxx/proto-center.git $branch

          fi

          mkdir -p python

          cp -r **/*.proto python

          pkgName=$( grep "\\-->" README.md | tail -1 | awk -F "-->v" '{print $1}' | awk -F " " '{print $2}')

          pkgName=${pkgName//-/_}

          echo $pkgName

          export pip_pkg_name=$pkgName

          echo $version

          export pip_tag_name=$version

    PIP install grpcio - tools = = 1.4.0

    PIP install protobuf = = 3.3.0

    echo "#! /bin/env python

          # -*- encoding=utf8 -*-

          import os

          from setuptools import (setup, find_packages)

          version = os.getenv('pip_tag_name')

          name = os.getenv('pip_pkg_name')

          setup(

            name=name,

            version=version,

            description='LLS grpc protocol',

            packages=find_packages(exclude=[]),

            include_package_data=True,

            author='LLS DEV Team',

            author_email='',

            package_data={'': ['*.*']},

            install_requires=[

    'grpcio = = 1.18.0',

    'protobuf = = 3.3.0'

    ].

            zip_safe=False,

            classifiers=[

              'Programming Language :: python :: 2.7',

    ].

          )" > python/setup.py

          echo "

          [distutils]

          index-servers = internal



          [internal]

          repository: https://xxxxx.com/

          username: xxxxx

          password: xxxxx

          " > ~/.pypirc

          mkdir -p python/$pkgName

          echo "
    " >> python/$pkgName/__init__.py

          python -m grpc_tools.protoc -I python/ --python_out=python/$pkgName --grpc_python_out=python/$pkgName/ python/*.proto

          cd python

          ls

          python setup.py bdist_wheel upload -r internal

          cd ..

          mkdir -p python3

          cp -r -n **/*.proto python3

          echo "
    #! /bin/env python

          # -*- encoding=utf8 -*-

          import os

          from setuptools import (setup, find_packages)

          version = os.getenv('pip_tag_name')

          name = os.getenv('pip_pkg_name')

          setup(

          name=name,

          version=version,

          description='LLS grpc protocol',

          packages=find_packages(exclude=[]),

          include_package_data=True,

          author='LLS DEV Team',

          author_email='',

          package_data={'': '*. *'},

          install_requires=[

            'grpcio = = 1.18.0'.

            'protobuf = = 3.12.4'

          ].

          zip_safe=False,

          classifiers=[

            'Programming Language: python :: 3.7'.

          ].

          )" > python3/setup.py

          python3 -m pip install Grpcio - tools = = 1.4.0

          python3 -m pip install protobuf

          mkdir -p python3/$pkgName

          echo "" >> python3/$pkgName/__init__.py

          python3 -m grpc_tools.protoc -I python3/ --python_out=python3/$pkgName --grpc_python_out=python3/$pkgName/  python3/*.proto

          cd python3/$pkgName

          ls

          pb_files=`ls | grep -v '__init__' | grep -v 'grpc.py'`

          echo $pb_files

          need_replace_strs=()

          for pb_file in ${pb_files[@]}

          do

              prefix=${pb_file/.py/}

              after_handle_package_name=${prefix//_/__}

              need_replace_str="import $prefix as $after_handle_package_name"

              echo $need_replace_str

              need_replace_strs[${#need_replace_strs[@]}]="$need_replace_str"

              #echo ${need_replace_strs[0]}

          done

          

          need_replace_str_num=${#need_replace_strs[@]}

          all_files=`ls | grep -v '__init__'`

          echo $all_files

          for file in ${all_files[@]}

          do

              for ((i=0; i<$need_replace_str_num; i++))

              do

                  need_replace_str=${need_replace_strs[${i}]}

                  echo $need_replace_str

                  sed -i  "s/^$need_replace_str$/from . $need_replace_str/" $file

              done

          done

          echo "finished"

          cd .

          python3 setup.py bdist_wheel upload -r internal

          echo ${CI_COMMIT_SHA}

          noticeMail=$userMail

          content="\nversion: $version\nproject: $pkgName\ncommit:\nhttps://xxxxx/proto-center/commit/${CI_COMMIT_SHA}\n"

          curl --location --request POST 'https://xxxxx/webhookurl' \

          --header 'Content-Type: application/json' \

          --data-raw '{

              "msg": {

                  "content":"'
    "${content}"'"

    } '


    Copy the code
  • 8️ one after constructing notification, similar robots such as Dingding, wechat and Slack 🤖 notification can be added to the job of the central warehouse, which is convenient for us to know the situation of proto CICD process, and we can bring our COMMIT information and corresponding version.


conclusion

In general, there are many other solutions, each of which has its own advantages and disadvantages. The best solution is the one that suits the business system architecture and company organization. However, try to reduce the human process and improve efficiency by using tools.

Refer to the link

  • https://eddycjy.com/posts/where-is-proto
  • https://segmentfault.com/a/1190000022532645