<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
    <channel>
        <title>نوشته های عرفان</title>
        <link>https://virgool.io/feed/@erfun</link>
        <description>یه دولوپر که سعی می‌کنه عمیق و کم هزینه باشه، از هرچیزی که بلدم می‌نویسم تا مطمئن‌شم درست یادش گرفتم.</description>
        <language>fa</language>
        <pubDate>2026-06-16 16:12:49</pubDate>
        <image>
            <url>https://files.virgool.io/upload/users/679/avatar/xbXTov.png?height=120&amp;width=120</url>
            <title>عرفان</title>
            <link>https://virgool.io/@erfun</link>
        </image>

                    <item>
                <title>‏gRPC چیه و چطور کار میکنه؟</title>
                <link>https://virgool.io/coderlife/grpc-%DA%86%DB%8C%D9%87-%D9%88-%DA%86%D8%B7%D9%88%D8%B1-%DA%A9%D8%A7%D8%B1-%D9%85%DB%8C%DA%A9%D9%86%D9%87-do6ntx3geuvg</link>
                <description>قبلا از شروع این فصل بهتره قسمت‌های قبل درباره HTTP/2 و پروتوباف رو مطالعه کنید.این روزها مایکروسرویس‌ها ترند شدن و هرکس یه تعریف متفاوتی ازش داره، من سعی کردم خودم تعریف خاصی ازش نداشته باشم و فقط توضیح بدم تکنولوژی‌هاش چطور کار می‌کنند مایکروسرویس‌ها می‌تونند با زبان‌های مختلف نوشته بشن، فرض کنید شما یک فروشگاه اینترنتی دارید، شما یک سرویس فروش خواهید داشت، همچنین میتونید یک سرویس پرومشن داشته باشید که با یک زبان متفاوت نوشته شده باشه، قاعدتا باید یک سرویس دلیوری هم داشته باشید و که به سرویس فروش وصل باشه، همچنین یک سرویس یوزر که به هر سه سرویس وصل شده باشه، مثل تصویر پایین:خب پس شما چندتا سرویس دارید که بهم وصل شدن و باهم صحبت میکنند، حالا برای پیاده کردن این معماری شما به چندتا چیز نیاز دارید:یک استاندارد برای APIیک دیتا فرمت استانداردیک استاندارد برای خطاهالود بالانسرو خیلی چیزهای دیگه...یکی از محبوب ترین استانداردها برای پیاده کردن API استاندارد REST هست که از جیسون استفاده میکنه. ولی ما در این مطلب از gRPC استفاده میکنیم.موقع ساخت API باید به خیلی چیزها توجه کنید:نوع دیتا مدل‌تون چی باشه؟ جیسون؟ XML؟ یا حتی باینری؟باید به اندپوینت‌ها فکر کنید، برای مثال در رست یه اندپوینت میتونه این شکلی باشه:GET /api/v1/users/123/posts/456
or
POST /api/v1/users/123/postsیا چطور میشه از چندتا زبان برنامه نویسی استفاده کرد، به نظر پیچیده میرسه، ولی در gRPC همچین مشکلاتی نداریم.در gRPC فقط میگیم ما میخوایم این تایپ از دیتارو ارسال کنیم و این تایپ رو دریافت کنیم و تمام، لازم نیست درمورد چیز دیگه‌ای فکر کنیم. در این مطلب تمام چیزهایی که درمورد رست میدونیم رو کنار میذاریم و gRPC شروع میکنیم.‏ gRPC چیه؟‏gRPC یک فریمورک رایگان و اپن‌سورس که توسط گوگل توسعه داده شده.‏gRPC عضو CNCF هستش، مثل داکر، کوبرنتیز. پس پروژه مهمیه و توسعه داده میشه.و از همه مهمتر مدرن و سریعه، ساخته شده با استاندارد HTTP/2 هست، از استریم دیتا و زبانهای متفاوت پشتیبانی میکنه و ساخت پلاگین‌های اعتبارسنجی کاربران، لودبالانسینگ، لاگ و مانیتورینگ بسیار سادس.‏RPC چیه؟‏RPC مخفف Remote Procedure Call هستش، در کدهای کلاینت شما اینطور به نظر میرسه که شما دارید یک فانکشن رو مستقیما توی سرور اجرا می‌کنید. ‏RPC یک کانسپت جدید نیست، برای مثال CORBA اینو از قبل داشت.چطور شروع کنیم؟برای شروع شما باید مسیج و سرویس‌هایی در فایل پروتوباف تعریف کنید. (اگر آشنایی ندارید بهتره این مطلب رو بخونید)کدهای gRPC توسط کدجنریتور پروتوباف برای زبان شما ساخته میشه و کافیه فقط اونهارو به پروژه اضافه کنید.علاوه بر این شما میتونید توسط یک فایل پروتوباف برای ۱۲ زبان برنامه نویسی کدجنریت کنید و میلیونها ریکويست در ثانیه پردازش کنید (توجه داشته باشید فعلا نمیتونید برای php سرور gRPC راه اندازی کنید)برای شروع میتونید یه سری به وبسایت رسمی gRPC.org بزنید، حتی نمونه‌هایی برای جاوا اسکریپت (فرانت‌اند) داره که میتونید استفاده کنید. (چون در فرانت‌اند و جاوا اسکریپت تخصصی ندارم واردش نمیشم)فریمورک gRPC برای جاوا، گولنگ و سی به صورت اختصاصی با این زبان‌ها نوشته شدن، ولی برای بقیه مثل PHP, ruby, C#, python و... از لایبرری C استفاده میکنه، این خیلی مهمه چون اگر آپدیتی برای c بیاد شما میتونید انتظار داشته باشید زبان‌های زیرمجموعه خیلی سریع آپدیت بشن ولی برای گولنگ و جاوا ممکنه مدت بیشتری طول بکشه و برعکس.انواع API در gRPCدر نوع یونری (unary) شبیه به مدل کلاسیک که میشناسیم کلاینت یه ریکوئست به سرور میزنه و سرور یک پاسخ ارسال میکنه ولی بسیار سریعتر.درنوع سرور استریمینگ کلاینت یک درخواست ارسال میکنه و سرور با یک ارتباط TCP چندین پاسخ ارسال میکنه.در نوع کلاینت استریمینگ هم مثل مثال قبلیه با این تفاوت که کلاینت چندین درخواست ارسال میکنه و سرور فقط یک پاسخ ارسال میکنه.و در نوع آخر سرور و کلاینت می‌تونند به صورت غیر منظم چندیدن درخواست بهم ارسال و دریافت کنند.در یک فایل پروتو انواع API به این صورت تعریف میشندر فایل بالا حواستون به کلمه استریم قبل از هر مسیج باشه، این تفاوت بین انواع API رو مشخص میکنه.درحال حاضر گوگل در ثانیه ۱۰ میلیارد ریکوئست gRPC پردازش میکنه، پس اگر برای گوگل کار میکنه تو اسکیل ماهم کار میکنه و مشکل پرفورمنسی نداریم.امنیت در gRPCبه صورت پیش‌فرض شما باید برای سرویس‌تون SSL ست کنید، ولی میتونید این قابلیت رو خاموش هم بکنید.همچنین شما می‌تونید سیستم Auth برای سرویستون تعریف کنید که سعی میکنم تو این مطلب یکم راهنمایی کنم.‏gRPC VS REST‏gRPC از پروتوباف استفاده میکنه که باینریه، خیلی سریعتره و حجم پاینن‌تری داره، ولی رست از جیسون استفاده میکنه که استرینگه، حجم بیشتری داره و کندتره.‏gRPC از HTTP/2 استفاده میکنه که در سال ۲۰۱۵ معرفی شده و تاخیر کمی داره، رست از HTTP/1.1 استفاده میکنه که در سال ۱۹۹۷ معرفی شده و تاخیر زیادی در پاسخ دادن داره.‏gRPC از استریم دیتا پشتیبانی میکنه ولی رست نه.رست CRUD بیس هست، یعنی برای هر اندپوینت ۴ اکشن اضافه، حذف، دریافت و ویرایش دارید ولی gRPC فانکشن بیس هست، یعنی می‌تونید دقیقا مشخص کنید این اندپوینت خارج از این ۴ عمل چه کاری انجام میده.‏gRPC برای هر زبانی کدجنریتور داره، درصورتی که رست کدجنریتوری مثل پروتوباف نداره.‏gRPC خودش یک استاندارده و یک فریمورک داره، درصورتی که برای استفاده از استاندارد رست توی هر زبانی شما باید یک فریمورک خاص استفاده کنید که هرکدوم به شیوه متفاوتی پیاده میشن.آماده سازی محیط برای توسعه gRPC با زبان گولنگاگر شما از اینجا برید به صفحه گیت‌هاب gRPC برای گولنگ، میبینید که میگه دستور پایین رو بزنید تا فریمورک gRPC نصب بشه:go get -u google.golang.org/grpcو برای نصب پروتوباف سری به صفحه گیت پروتوباف میزنیم و میگه برای نصبش باید از دستور پایین استفاده کنیم:go get -u github.com/golang/protobuf/protoc-gen-goحالا یک ریپازیتوری روی گیت میسازیم و ساختار پروژه رو تعریف میکنیم و قصدمون ساخت یک ماشین حساب سادس.ماشین حساب سادهسه پوشه با اسم‌های calculator_client ، calculator_server ، calculatorpb ایجاد میکنیم.حالا فایلی با اسم calculator.proto داخل پوشه calculatorpb ایجاد میکنیم.syntax = &quot;proto3&quot;;

package calculator;
option go_package = &quot;calculatorpb&quot;;

message SumRequest {
    int32 first_number = 1;
    int32 second_umber = 2;
}

message SumResponse {
    int32 sum_result = 1;
}

service CalculatorService {
    // Unary
    rpc Sum (SumRequest) returns (SumResponse) {};
}اگر پست قبلی در مورد پروتوباف رو خونده باشید اینجا چیزی برای توضیح دادن نداریم، تنها نکته اینه یک سرویس rpc از نوع unary تعریف کردیم (که بالاتر توضیح دادیم یونری چیه) و داخل ترمینال از پوشه روت پروژه دستور زیر رو برای ساخت فایل پروتو وارد کردیم:protoc calculatorpb/calculator.proto --go_out=plugins=grpc:.اگر نیازمندی‌های بالا را درست نصب کرده باشید و فایل رو در پوشه درست ایجاد کرده باشید و دستور بالا رو از پوشه روت پروژه اجرا کرده باشید باید فایلی به اسم calculator.pb.go در پوشه calculatorpb ایجاد شده باشه. در مرحله بعد این فایل‌رو به پروژه ایمپورت میکنیم و برای ساخت gRPC سرور و کلاینت ازش استفاده میکنیم.ایجاد سرور یونری Unary gRPCفایل main.go در پوشه calculator_server همراه با محتوای زیر ایجاد میکنید:package main

import (
   &quot;context&quot;
   &quot;fmt&quot;
   &quot;github.com/ErFUN-KH/simple-grpc-project/calculatorpb&quot;
   &quot;google.golang.org/grpc&quot;
   &quot;log&quot;
   &quot;net&quot;
)

type server struct{}

func main() {
   fmt.Println(&quot;Server is running...&quot;)

   // Make a listener
   lis, err := net.Listen(&quot;tcp&quot;, &quot;0.0.0.0:50051&quot;)
   if err != nil {
      log.Fatalf(&quot;Failed to listen: %v&quot;, err)
   }

   // Make a gRPC server
   grpcServer := grpc.NewServer()
   calculatorpb.RegisterCalculatorServiceServer(grpcServer, &amp;server{})

   // Run the gRPC server
   if err := grpcServer.Serve(lis); err != nil {
      log.Fatalf(&quot;Failed to serve: %v&quot;, err)
   }
}

func (*server) Sum(ctx context.Context, req *calculatorpb.SumRequest) (*calculatorpb.SumResponse, error) {
   fmt.Printf(&quot;Received Sum RPC: %v&quot;, req)

   firstNumber := req.GetFirstNumber()
   secondNumber := req.GetSecondUmber()

   sum := firstNumber + secondNumber

   res := &amp;calculatorpb.SumResponse{
      SumResult: sum,
   }

   return res, nil
}فکر میکنم تا خط ۱۸ چالشی نداره پس از اینجا شروع میکنم فقط قبلش باید بگم اون server که در خط ۱۲ تعریف کردم تمام اندپوینت‌هارو در خودش نگه میداره و یه جورایی میشه گفت مشابه فایل route در رست‌فوله، در خط ۱۸ یک پورت TCP باز میکنیم تا سرویس gRPC بتونه ازش استفاده کنه.در خط ۲۴ یک سرور gRPC تعریف کردیم، و در خط ۲۵ سرویس ماشین‌حساب‌رو روش کانفیگ کردیم (که از فایل پروتویی که جنریت کردیم ایمپورت شده) و در خط ۲۸ سرور gRPC رو اجرا کردیم. یک سرور gRPC به صورت کلی همچین چیزیه.و در خط ۳۳ یک اندپوینت برای جمع اعداد ایجاد کردیم، کدها به قدری سادس که اگر گولنگ بلد باشید هیچ توضیحی نیاز نداره. در خط ۳۶ و ۳۷ اعداد اول و دوم رو دریافت کردیم، بعد از اون باهم جمع بستیم و در خط ۴۱ یک ریسپانس از جنس جمع اعداد ایجاد کردیم و به کاربر فرستادیم.حالا اگر پروژه رو بیلد بگیرید میبینید که سرور به درستی اجرا میشه.تا اینجای کار میتونید پروژه رو در گیت ببینید.ایجاد کلاینت یونری Unary gRPCفایل main.go در پوشه calculator_client همراه با محتوای زیر ایجاد میکنید:package main

import (
   &quot;context&quot;
   &quot;fmt&quot;
   &quot;github.com/ErFUN-KH/simple-grpc-project/calculatorpb&quot;
   &quot;google.golang.org/grpc&quot;
   &quot;log&quot;
)

func main() {
   fmt.Println(&quot;Client is running...&quot;)

   cc, err := grpc.Dial(&quot;localhost:50051&quot;, grpc.WithInsecure())
   if err != nil {
      log.Fatalf(&quot;could not connect to server: %v&quot;, err)
   }
   defer cc.Close()

   c := calculatorpb.NewCalculatorServiceClient(cc)

   doSum(c)
}

func doSum(c calculatorpb.CalculatorServiceClient) {
   fmt.Println(&quot;Starting to do a sum RPC&quot;)

   req := &amp;calculatorpb.SumRequest{
      FirstNumber: 40,
      SecondUmber: 2,
   }

   res, err := c.Sum(context.Background(), req)
   if err != nil {
      log.Fatalf(&quot;Error while calling sum RPC: %v&quot;, err)
   }

   log.Printf(&quot;Response from server: %v&quot;, res.SumResult)
}برای کلاینت هم کدها ساده و کمه، در خط ۱۴ یک تماس با سرور gRPC ایجاد کردیم توجه داشته باشید چون فعلا نمیخوایم کلید ssl ست تنظیم کنیم از متد grpc.WithInsecure استفاده کردیم ولی در آینده توضیح میدم چطور ست کنید.در خط ۲۰ به کمک کدی که توسط فایل پروتو جنریت شده بود یک سرویس کلاینت ماشین حساب رو ایجاد کردیم.و درمحله بعد یک فانکش صدا میکنیم که جمع دو عدد رو از سرور بگیره، در خط ۲۸ دو عدد رو ست کردیم، در خط ۳۳ اندپوینت رو کال کردیم و جواب رو گرفتیم و در خط پایینی پرینت گرفتم.حالا اول سرور رو بیلد و اجرا کنید و بعد کلاینت رو، می‌بینید اعداد باهم جمع میخورن و در ترمینال نمایش داده میشه. تبریک میگم شما اول سرویس‌تون رو نوشتید :)از اینجا می‌تونید کل پروژه‌رو روی گیت ببینید.سرور استریمینگ  gRPCسرور استریمینگ gRPC یک نوع جدید از API هست که با وجود HTTP/2 امکان پذیر شده.در این نوع از API کلاینت یک مسیج به سرور ارسال میکنه و بیشتر از یک ریسپانس از سرور دریافت میکنه (در تعداد ریسپانس‌ها محدودیتی وجود نداره، میتونه نامحدود باشه).از موارد استفاده این میشه به دانلود فایل‌های سنگین از سرور اشاره کرد، فایل چندگیگی به چانک‌های یک گیگی تقسیم کنید و ارسال کنید، اگر خطایی پیش بیاد کل فایل خراب نمیشه و فقط اون چانک رو دوباره دانلود میکنید نه کل فایل رو. یا درمورد دیگه میشه در موارد لایو دیتاها استفاده کرد.میخوایم یک اندپوینت بنویسیم که فاکتوریل اعداد رو محاسبه کنه، اول از همه فایل پروتو باز میکنیم و به این صورت ادیتش میکنیم:syntax = &quot;proto3&quot;;

package calculator;
option go_package = &quot;calculatorpb&quot;;

message SumRequest {
    int32 first_number = 1;
    int32 second_umber = 2;
}

message SumResponse {
    int32 sum_result = 1;
}

message PrimeNumberDecompositionRequest {
    int64 number = 1;
}

message PrimeNumberDecompositionResponse {
    int64 prime_factor = 1;
}

service CalculatorService {
    // Unary
    rpc Sum (SumRequest) returns (SumResponse) {};

    // Server Streaming
    rpc PrimeNumberDecomposition (PrimeNumberDecompositionRequest) returns (stream PrimeNumberDecompositionResponse) {};
}در خط‌های ۱۵ و ۱۹ دو مسیج جدید و در خط ۲۸ یک سرویس جدید از نوع سرور استریمینگ تعریف کردیم (به کلمه stream در خط ۲۸ توجه کنید) و بعد از اون دوباره فایل پروتو رو با دستور پایین جنریت کردیم.protoc calculatorpb/calculator.proto --go_out=plugins=grpc:.حالا فایل calculator_server/main.go باز میکنیم و فانکشن زیر رو به آخرین خط اضافه می‌کنیم:func (*server) PrimeNumberDecomposition(req *calculatorpb.PrimeNumberDecompositionRequest, stream calculatorpb.CalculatorService_PrimeNumberDecompositionServer) error {
   fmt.Printf(&quot;Received PrimeNumberDecomposition RPC: %v\n&quot;, req)

   number := req.Number
   divisor := int64(2)

   for number &gt; 1 {
      if number % divisor == 0 {
         err := stream.Send(&amp;calculatorpb.PrimeNumberDecompositionResponse{
            PrimeFactor: divisor,
         })
         if err != nil {
            log.Fatalf(&quot;Failed to send response: %v\n&quot;, err)
         }

         number = number / divisor
      } else {
         divisor++
         fmt.Printf(&quot;Divisor has increased to %v&quot;, divisor)
      }
   }

   return nil
}در خط اول می‌بینیم متدهای این فانکشن با فانکشن قبلی فرق داره، بهتره فایل جنریت شده توسط پروتوباف رو بازکنید و متدهارو چک کنید (یک فانکشن دقیقا با همین اسم و متدها میبینید، می‌تونید کپی پیست کنید) و بعد فانکشن رو تعریف کنید.در خط‌های بعدی عدد ورودی توسط کلاینت رو دریافت کردیم و فاکتوریل‌شو محاسبه کردیم، در خط ۹ اعداد محاسبه شده رو به کلاینت ارسال کردیم (اگر متدهای استریم رو چک کنید میبینید چندتایی هست که اینجا از  send استفاده کردیم ولی با توجه به نوع API متدهای متفاوتی داره).کارمون با سرور تمومه، فایل calculator_client/main.go باز میکنیم و به این صورت تغییرش میدیم:package main

import (
   &quot;context&quot;
   &quot;fmt&quot;
   &quot;github.com/ErFUN-KH/simple-grpc-project/calculatorpb&quot;
   &quot;google.golang.org/grpc&quot;
   &quot;io&quot;
   &quot;log&quot;
)

func main() {
   fmt.Println(&quot;Client is running...&quot;)

   cc, err := grpc.Dial(&quot;localhost:50051&quot;, grpc.WithInsecure())
   if err != nil {
      log.Fatalf(&quot;could not connect to server: %v&quot;, err)
   }
   defer cc.Close()

   c := calculatorpb.NewCalculatorServiceClient(cc)

   //doSum(c)

   doServerStreaming(c)
}

func doSum(c calculatorpb.CalculatorServiceClient) {
   fmt.Println(&quot;Starting to do a sum Unary RPC&quot;)

   req := &amp;calculatorpb.SumRequest{
      FirstNumber: 40,
      SecondUmber: 2,
   }

   res, err := c.Sum(context.Background(), req)
   if err != nil {
      log.Fatalf(&quot;Error while calling sum RPC: %v&quot;, err)
   }

   log.Printf(&quot;Response from server: %v&quot;, res.SumResult)
}

func doServerStreaming(c calculatorpb.CalculatorServiceClient) {
   fmt.Println(&quot;Starting to do a PrimeDecomposition server streaming RPC&quot;)

   req := &amp;calculatorpb.PrimeNumberDecompositionRequest{
      Number: 12,
   }

   stream, err := c.PrimeNumberDecomposition(context.Background(), req)
   if err != nil {
      log.Fatalf(&quot;Error while calling PrimeDecomposition RPC: %v&quot;, err)
   }

   for {
      res, err := stream.Recv()
      if err == io.EOF {
         break
      }
      if err != nil {
         log.Printf(&quot;Error while streaming PrimeDecomposition RPC: %v&quot;, err)
      }
      fmt.Println(res.PrimeFactor)
   }
}در خط ۲۳ اندپوینت قبلی رو غیرفعال کردیم و در خط بعدی اندپوینت جدیدی رو صدا زدیم، در خط ۴۴ مثل اندپوینت قبلی یک ریکوئست پروتوباف درست کردیم بعد از اون اندپوینت محاسبه فاکتوریل رو  از سرور کال کردیم، ولی برای گرفتن پاسخ از سرور یک حلقه تعریف کردیم، در خط ۵۷ دیتای استریم رو دریافت کردیم (اینجا از متد Recv استفاده کردیم که اگه یادتون باشه توی سرور از متد send استفاده کرده بودیم)، درخط بعد شرط گذاشتیم اگر استریم به پایان رسید از حلقه خارج بشیم، بعد از اون یک شرط گذاشتیم اگر به هر دلیلی استریم خطا داشت، خطا رو نمایش بده و بعد از اون اگر مشکلی وجود نداشت جواب دریافتی از سرور رو چاپ کردیم.کدهای این بخش در گیت‌هاب.کلاینت استریمینگ gRPCتوضیحات رو کوتاه میکنم، اینم دقیقا مثل سرور استریمینگه با این تفاوت که کاربر تعداد زیای ریکوئست ارسال میکنه و سرور فقط یک پاسخ ارسال میکنه.موارد استفاده هم میشه برای آپلود فایل و... استفاده کرد.پس (برای محاسبه کردن میانگین یک مجموعه اعداد) فایل پروتو به این صورت تغییر میدیم:syntax = &quot;proto3&quot;;

package calculator;
option go_package = &quot;calculatorpb&quot;;

message SumRequest {
    int32 first_number = 1;
    int32 second_umber = 2;
}

message SumResponse {
    int32 sum_result = 1;
}

message PrimeNumberDecompositionRequest {
    int64 number = 1;
}

message PrimeNumberDecompositionResponse {
    int64 prime_factor = 1;
}

message ComputeAverageRequest {
    int32 number = 1;
}

message ComputeAverageResponse {
    double average = 1;
}

service CalculatorService {
    // Unary
    rpc Sum (SumRequest) returns (SumResponse) {};

    // Server Streaming
    rpc PrimeNumberDecomposition (PrimeNumberDecompositionRequest) returns (stream PrimeNumberDecompositionResponse) {};

    // Client Streaming
    rpc ComputeAverage (stream ComputeAverageRequest) returns (ComputeAverageResponse) {};
}در خط‌های ۲۳ و ۲۷ دو مسیج جدید تعریف کردیم و در خط ۳۹ یک اندپوینت کلاینت استریمینگ تعریف کردیم.حالا با دستور زیر دوباره فایل پروتو رو برای استفاده در کد، جنریت میکنیم:protoc calculatorpb/calculator.proto --go_out=plugins=grpc:.حالا فایل calculator_server/main.go باز میکنیم و فانکشن زیر رو بهش اضافه میکنیم:func (*server) ComputeAverage(stream calculatorpb.CalculatorService_ComputeAverageServer) error {
   fmt.Printf(&quot;Received ComputeAverage RPC\n&quot;)

   sum := float64(0)
   count := float64(0)

   for {
      req, err := stream.Recv()

      if err == io.EOF {
         return stream.SendAndClose(&amp;calculatorpb.ComputeAverageResponse{
            Average: sum / count,
         })
      }
      if err != nil {
         log.Fatalf(&quot;Error while reading client stream: %v&quot;, err)
      }

      sum += float64(req.GetNumber())
      count++
   }
}اگر فایل calculator/calculator.proto باز کنید و کلمه Server interface سرچ کنید میتونید فانکشن ComputeAverage همراه با متدهاشو ببینید و استفاده کنید، عین کاری که من میکنم.در خط ۴ و ۵ دو متغییر جمع تمام اعداد و تعدادشون ایجاد کردیم.و بعد یک حلقه ایجاد کردیم و دیتاهای ارسالی از سمت کاربر رو دریافت کردیم، بعد از اون یک شرط گذاشتیم اگر دریتاهای ارسالی تموم شده باشه جواب رو به کاربر ارسال کنه (از متد SendAndClose استفاده کردیم) وگرنه چک میکنه اگر موقع دریافت دیتا مشکل بخوره خطا رو نمایش بده، بعد اون اگر مشکلی نباشه عدد دریافتی از کاربر رو با اعداد دریافتی قبلی جمع میزنیم و یکی به تعداد کل اعداد اضافه میکنیم تا بعد بتونیم میانگین بگیریم.حالا در بخش کلاینت فانکش زیر رو اضافه و در تابع اصلی صداش میکنیم:func doClientStreaming(c calculatorpb.CalculatorServiceClient) {
   fmt.Println(&quot;Starting to do a ComputeAverage client streaming RPC&quot;)

   stream, err := c.ComputeAverage(context.Background())
   if err != nil {
      log.Fatalf(&quot;Error while calling stream RPC: %v&quot;, err)
   }

   numbers := []int32{2, 5, 7, 9, 12, 57}

   for _, number := range numbers {
      fmt.Printf(&quot;Sending number: %v\n&quot;, number)
      err := stream.Send(&amp;calculatorpb.ComputeAverageRequest{
         Number:number,
      })
      if err != nil {
         log.Fatalf(&quot;Error while sending stream: %v&quot;, err)
      }
   }

   res, err := stream.CloseAndRecv()
   if err != nil {
      log.Fatalf(&quot;Error while receiving response: %v&quot;, err)
   }

   fmt.Printf(&quot;The average is: %v\n&quot;, res.GetAverage())
}در خط ۴ اندپوینت میانگین گیری در سرور رو صدا زدیم، در خط ۱۱ یک حلقه ایجاد کردیم تا دیتاهارو به سرور ارسال کنه، در خط ۲۱ استریم رو قطع و پیام سرور رو دریافت میکنیم (از متد CloseAndRecv استفاده کردیم) و اگر خطایی اتفاق نیفته میانگین اعداد رو چاپ میکنیم.کدهای این بخش رو در گیت‌هاب ببینید.استریمینگ دوطرفهدر ارتباط دوطرفه یا Bi Directional کلاینت میتونه هر تعداد که میخواد ریکوئست به سرور ارسال کنه و سرور هم میتونه به هر کدوم از ریکويست‌هایی که میخواد پاسخ بده.از موارد استفاده این نوع از API میشه برای سیستم چت و... نامبرد.مثل همیشه فایل پروتو آپدیت میکنیم:syntax = &quot;proto3&quot;;

package calculator;
option go_package = &quot;calculatorpb&quot;;

message SumRequest {
    int32 first_number = 1;
    int32 second_umber = 2;
}

message SumResponse {
    int32 sum_result = 1;
}

message PrimeNumberDecompositionRequest {
    int64 number = 1;
}

message PrimeNumberDecompositionResponse {
    int64 prime_factor = 1;
}

message ComputeAverageRequest {
    int32 number = 1;
}

message ComputeAverageResponse {
    double average = 1;
}

message FindMaximumRequest {
    int32 number = 1;
}

message FindMaximumResponse {
    int32 maximum = 1;
}

service CalculatorService {
    // Unary
    rpc Sum (SumRequest) returns (SumResponse) {};

    // Server Streaming
    rpc PrimeNumberDecomposition (PrimeNumberDecompositionRequest) returns (stream PrimeNumberDecompositionResponse) {};

    // Client Streaming
    rpc ComputeAverage (stream ComputeAverageRequest) returns (ComputeAverageResponse) {};

    // BiDi Streaming
    rpc FindMaximum(stream FindMaximumRequest) returns (stream FindMaximumResponse) {};
}و بعد کدهارو جنریت میکنیم:protoc calculatorpb/calculator.proto --go_out=plugins=grpc:.حالا فایل calculator_server/main.go باز میکنیم و فانکشن زیر رو بهش اضافه میکنیم:func (*server) FindMaximum(stream calculatorpb.CalculatorService_FindMaximumServer) error {
   fmt.Printf(&quot;Received FindMaximum RPC\n&quot;)

   maximum := int32(0)

   for {
      req, err := stream.Recv()
      if err == io.EOF {
         return nil
      }
      if err != nil {
         log.Fatalf(&quot;Error while reading client stream: %v&quot;, err)
         return err
      }

      if req.Number &gt; maximum {
         maximum = req.Number
         err := stream.Send(&amp;calculatorpb.FindMaximumResponse{
            Maximum: maximum,
         })
         if err != nil {
            log.Fatalf(&quot;Error while sending client stream: %v&quot;, err)
            return err
         }
      }
   }
}اول از همه یک متغییر ایجاد کردیم تا مقدار بزرگترین عدد توش ذخیره کنیم.بعد اون یک حلقه ایجاد کردیم تا استریم دیتارو دریافت و پردازش کنیم، در خط اول این حلقه استریم رو دریافت کردیم، بعد اون به پایان رسیدن استریم رو چک کردیم که اگر استریم تموم شده باشه از حلقه خارج میشه، بعد اون خطاهایی که ممکنه به علت قطع شبکه یا... پیش بیاد رو چک کردیم، بعد از اون اگر همه چیز درست باشه چک میکنیم اگر عدد دریافتی از ماکسیمومی که ما داشتیم بزرگتر بود یک ریسپانس به کلاینت ارسل میکنیم و اگر کوچیکتر بود هیچ اکشنی نداریم. (در اینجا اگر متدهای استریم رو چک کنید میبینید که هم سند داریم و هم رسیو داریم، درصورتی که در API های قبلی فقط یکی رو داشتیم)حالا برای کلاینت از گو روتین استفاده میکنیم که حتما باید بلد باشید وگرنه درک این قسمت براتون سخت میشه (concurrency)، کد زیر رو به کلاینت اضافه میکنیم:func doBiDiStreaming(c calculatorpb.CalculatorServiceClient) {
   fmt.Println(&quot;Starting to do a FindMaximum BiDi streaming RPC&quot;)

   stream, err := c.FindMaximum(context.Background())
   if err != nil {
      log.Fatalf(&quot;Error while calling stream RPC: %v&quot;, err)
   }

   waitingForChannel := make(chan struct{})

   // send go routine
   go func() {
      numbers := []int32{2, 8, 1, 5, 37, 28, 42}

      for _, number := range numbers {
         err := stream.Send(&amp;calculatorpb.FindMaximumRequest{
            Number: number,
         })
         if err != nil {
            log.Fatalf(&quot;Error while sending stream: %v&quot;, err)
         }
         time.Sleep(1000 * time.Millisecond)
      }

      err := stream.CloseSend()
      if err != nil {
         log.Fatalf(&quot;Error while closing stream: %v&quot;, err)
      }
   }()

   // receive go routine
   go func() {
      for {
         res, err := stream.Recv()
         if err == io.EOF {
            break
         }
         if err != nil {
            log.Fatalf(&quot;Error while receving stream: %v&quot;, err)
            break
         }

         fmt.Printf(&quot;New maximun is: %v\n&quot;, res.Maximum)
      }
      close(waitingForChannel)
   }()

   &lt;-waitingForChannel
}اگر خیلی ساده بخوام توضیح بدم باید بگم دوتا تابع به صورت همزمان برای دریافت و ارسال دیتا ایجاد کردیم، داخل هرکدوم یک حلقه نوشتیم، برای قسمت ارسال دیتا توسط گو روتین اول یک آرایه از اعداد درست کردیم، توسط یک حلقه تمام اعداد آرایه رو به سرور ارسال کردیم و گفتیم بعد هر ارسال یک ثانیه صبر کن (تا تاخیر داشته باشه و استریم رو بهتر درک کنیم) و بعد از تمام شدن حلقه گفتیم استریم ارسال دیتارو ببند.و حالا برای دریافت دیتا یک گو روتین دیگه تعریف کردیم داخلش یک حلقه بینهایت نوشتیم، دیتا از سمت سرور رو دریافت کردیم، اگر استریم به پایان رسیده باشه حلقه رو میشکونیم، همینطور اگر خطایی اتفاق بیفته بازم حلقه رو میشکونیم، بنابراین اگر دیتا بصورت عادی دریافت بشه در صفحه نمایش چاپ میشه، بعد از پایان رسیدن استریم حلقه تموم میشه و چنلی که ایجاد کرده بودیم بسته میشه و اپلیکیشن به کارش پایان میده.کدهای این بخش رو در گیت‌هاب ببینید.خطاهاوقتی برای API مشکلی پیش بیاد سرور ما باید خطا برگردونه، در رست از کدهای خطای HTTP استفاده میکنیم ۲۰۰ برای موفقت آمیز بودن، ۳۰۰ برای ری‌دایرکت کردن، ۴۰۰ برای خطاهای داخلی و ۵۰۰ برای خطاهای سرور.برای دیدن خطاهای gRPC میتونید از داکیومنت رسمی کمک بگیرید، همینطور که میبینید خطاها در gRPC دارای یک کد و یک مسیج هستند، تشخیص نوع خطا از روی کد هستش و مسیج بیشتر برای ساده کردن دیباگه.برای نمونه میخوایم اندپوینتی بنویسم که ریشه دوم اعداد رو بهمون برگردونه، بازم مثل همیشه اول فایل پروتو آپدیت میکنیم:syntax = &quot;proto3&quot;;

package calculator;
option go_package = &quot;calculatorpb&quot;;

message SumRequest {
    int32 first_number = 1;
    int32 second_umber = 2;
}

message SumResponse {
    int32 sum_result = 1;
}

message PrimeNumberDecompositionRequest {
    int64 number = 1;
}

message PrimeNumberDecompositionResponse {
    int64 prime_factor = 1;
}

message ComputeAverageRequest {
    int32 number = 1;
}

message ComputeAverageResponse {
    double average = 1;
}

message FindMaximumRequest {
    int32 number = 1;
}

message FindMaximumResponse {
    int32 maximum = 1;
}

message SquareRootRequest {
    int32 number = 1;
}

message SquareRootResponse {
    double number_root = 1;
}

service CalculatorService {
    // Unary
    rpc Sum (SumRequest) returns (SumResponse) {};

    // Server Streaming
    rpc PrimeNumberDecomposition (PrimeNumberDecompositionRequest) returns (stream PrimeNumberDecompositionResponse) {};

    // Client Streaming
    rpc ComputeAverage (stream ComputeAverageRequest) returns (ComputeAverageResponse) {};

    // BiDi Streaming
    rpc FindMaximum (stream FindMaximumRequest) returns (stream FindMaximumResponse) {};

    // Error Handing
    rpc SquareRoot (SquareRootRequest) returns (SquareRootResponse) {};
}و در مرحله بعد فایلشو جنریت میکنیم:protoc calculatorpb/calculator.proto --go_out=plugins=grpc:.حالا برای سرور این اندپوینت رو اینجاد میکنیم:func (*server) SquareRoot(ctx context.Context, req *calculatorpb.SquareRootRequest) (*calculatorpb.SquareRootResponse, error) {
   fmt.Println(&quot;Received SquareRoot RPC&quot;)

   number := req.Number

   if number &lt; 0 {
      return nil, status.Errorf(
         codes.InvalidArgument,
         fmt.Sprintf(&quot;Received a negative number: %v&quot;, number),
         )
   }

   return &amp;calculatorpb.SquareRootResponse{
      NumberRoot: math.Sqrt(float64(number)),
   }, nil
}همیشه چیز مثل همون اندپوینت یونری در قبله، تنها تفاوت اونجایی که چک کردیم اگر عدد کوچیکتر از صفر بود یک خطا برگردونه، همینطور که میبنید میتونید یکی از کدهای استاندارد خطاهای gRPC استفاده کنید و یک پیام خطای شخصی سازی شده.حالا برای کلاینت:func doSquareRoot(c calculatorpb.CalculatorServiceClient, number int32) {
   fmt.Println(&quot;Starting to do a SquareRoot Unary RPC&quot;)

   res, err := c.SquareRoot(context.Background(), &amp;calculatorpb.SquareRootRequest{Number: number})
   if err != nil {
      resError, ok := status.FromError(err)
      if ok {
         // Actual error from gRPC (user error)
         fmt.Printf(&quot;Error message from server: %v\n&quot;, resError.Message())
         fmt.Println(resError.Code())
         if resError.Code() == codes.InvalidArgument {
            log.Fatalln(&quot;We probably sent a negative number&quot;)
         }
      } else {
         log.Fatalf(&quot;Big error calling SquareRoot: %v&quot;, err)
      }
   }
   fmt.Printf(&quot;Result of square root of %v: %v\n\n&quot;, number, res.NumberRoot)
}در کلاینت هم همه چی شبیه قبله با این تفاوت که در متدهای دریافتی یک عدد هم دریافت کردیم تا بتونیم دو دفعه در تابع main صداش کنیم، یکبار همراه با خطا و یک دفعه بدون خطا.کدها مثل صدا کردن یونری در قبله ولی بعد از چک کردن خطا یک شرط دیگم گذاشتیم، اینجا با کمک status.FromError چک میکنیم آیا خطای دریافتی از استاندارد gRPC هست یا نه، اگر بود پیام خطا و کد رو چاپ میکنیم، اگر نبود خطا رو چاپ میکنیم.کدهای این بخش رو از گیت‌هاب بخونید.ددلاین - Dead lineیک ددلاین مشخص میکنه یک RPC حداکثر ممکنه چقدر طول بکشه تا جواب کلاینت رو بده و اگر در طول این مدت پاسخی ارسال نشه خطای DEADLINE_EXCEEDED برگردونه.داکیومنت رسمی gRPC شدیدا توصیه میکنه برای هر تمام کلاینت‌های RPC ددلاین مشخص کنید.احتمالا این سوال براتون پیش میاد که  ددلاین‌هارو باید چقدر ست کنیم؟ این به شما بستگی داره، فکر میکنید چقدر طول میکشه تا API شما پردازش رو انجام بده؟ برای یک API معمولی ممکنه ۱۰۰ میلی ثانیه کافی باشه، یا اگر خیلی کند باشه ۱ ثانیه باید کافی باشه، ولی به شما بستگی داره که فکر میکنید چقدر ممکنه طول بکشه، هیچ قانونی براش نداریم میتونید حتی ۵ دقیقه  هم ست کنید.سرور باید چک کنه، اگر ددلاین به پایان رسیده باید پردازش رو متوقف کنه.  برای مثال ددلاین ۱ دقیقه ست شده و سرور نتونه توی ۱ دقیقه کار رو انجام بده، باید پردازش رو متوقف کنه و به کلاینت بگه نتونستم توی ۱ دقیقه کار رو تموم کنم.اینجا یک پست درمورد ددلاین با مثال از وبلاگ رسمی gRPC هست که پیشنهاد میکنم ببینید.حالا میخوایم جمع دو عدد رو با ددلاین انجام بدیم، مثل همیشه اول فایل پروتو آپدیت میکنیم:syntax = &quot;proto3&quot;;

package calculator;
option go_package = &quot;calculatorpb&quot;;

message SumRequest {
    int32 first_number = 1;
    int32 second_umber = 2;
}

message SumResponse {
    int32 sum_result = 1;
}

message PrimeNumberDecompositionRequest {
    int64 number = 1;
}

message PrimeNumberDecompositionResponse {
    int64 prime_factor = 1;
}

message ComputeAverageRequest {
    int32 number = 1;
}

message ComputeAverageResponse {
    double average = 1;
}

message FindMaximumRequest {
    int32 number = 1;
}

message FindMaximumResponse {
    int32 maximum = 1;
}

message SquareRootRequest {
    int32 number = 1;
}

message SquareRootResponse {
    double number_root = 1;
}

message SumWithDeadLineRequest {
    int32 first_number = 1;
    int32 second_umber = 2;
}

message SumWithDeadLineResponse {
    int32 sum_result = 1;
}

service CalculatorService {
    // Unary
    rpc Sum (SumRequest) returns (SumResponse) {};

    // Server Streaming
    rpc PrimeNumberDecomposition (PrimeNumberDecompositionRequest) returns (stream PrimeNumberDecompositionResponse) {};

    // Client Streaming
    rpc ComputeAverage (stream ComputeAverageRequest) returns (ComputeAverageResponse) {};

    // BiDi Streaming
    rpc FindMaximum (stream FindMaximumRequest) returns (stream FindMaximumResponse) {};

    // Error Handing
    rpc SquareRoot (SquareRootRequest) returns (SquareRootResponse) {};

    // Dead Line
    rpc SumWithDeadLine (SumWithDeadLineRequest) returns (SumWithDeadLineResponse) {};
}بعد از اون کدهارو جنریت میکنیم:protoc calculatorpb/calculator.proto --go_out=plugins=grpc:.حالا برای سرور به اینصورت عمل میکنیم:func (*server) SumWithDeadLine(ctx context.Context, req *calculatorpb.SumWithDeadLineRequest) (*calculatorpb.SumWithDeadLineResponse, error) {
   fmt.Printf(&quot;Received SumWithDeadLine RPC: %v\n&quot;, req)

   for i := 0; i &lt; 3; i++ {

      if ctx.Err() == context.Canceled {
         fmt.Println(&quot;The client canceled the request&quot;)
         return nil, status.Error(codes.Canceled, &quot;The client canceled the request&quot;)
      }

      time.Sleep(1 * time.Second)
   }

   firstNumber := req.GetFirstNumber()
   secondNumber := req.GetSecondUmber()

   sum := firstNumber + secondNumber

   res := &amp;calculatorpb.SumWithDeadLineResponse{
      SumResult: sum,
   }

   return res, nil
}سرور مشابه همون جمع دو عدده فقط یک حلقه بهش اضافه کردیم، حلقه ۳ بار اجرا میشه و هربار چک میکنه ددلاین به پایان رسیده یا نه، اگر رسیده باشه خطارو چاپ میکنه اگر نرسیده باشه ۱ ثانیه تاخیر ایجاد میکنه.و برای کلاینت:func doSumWithDeadLine(c calculatorpb.CalculatorServiceClient, time time.Duration) {
   fmt.Println(&quot;Starting to do a SumWithDeadLine RPC&quot;)

   req := &amp;calculatorpb.SumWithDeadLineRequest{
      FirstNumber: 40,
      SecondUmber: 2,
   }

   ctx, cancel := context.WithTimeout(context.Background(), time)
   defer cancel()

   res, err := c.SumWithDeadLine(ctx, req)
   if err != nil {
      resError, ok := status.FromError(err)
      if ok {
         if resError.Code() == codes.DeadlineExceeded {
            log.Fatalln(&quot;Timeout was hit! Deadline was exceeded&quot;)
         } else {
            log.Fatalf(&quot;Unexpected error: %v&quot;, err)
         }
      } else {
         log.Fatalf(&quot;Error while calling sum RPC: %v&quot;, err)
      }
   }

   log.Printf(&quot;Response from server: %v\n\n&quot;, res.SumResult)
}تمام کد مشابه جمع بستن دو عدده، با این تفاوت که یک context.WithTimeout تعریف کردیم و تایم اوت رو توش ست کردیم.بعد از اون در قسمت خطاها چک کردیم اگر ددلاین به پایان رسیده خطا رو چاپ کنه. به همین سادگی. از اونجایی که سرور ۳ ثانیه تاخیر داره ما دو دفعه این فانکشن رو صدا میزنیم، یکبار با ددلاین ۵ ثانیه یکبار با ددلاین ۳ قانیه، مورد اول باید پاس بشه و مورد دوم باید خطای ددلاین بده.کدهای این بخش رو در گیت‌هاب ببینید.‏SSLدر پروداکشن بهتره سرویس‌های gRPC شما با SSL بهم متصل بشن، این پیش فرض HTTP/2 هستش و gRPC هم بهش احترام میذاره.‏ssl بهتون کمک میکنه اینکریپشن e2e داشته باشید و هیچکس نتونه دیتای ارسالی سرویس‌ها بهم رو شنود کنه یا تغییر بده.دو نوع اتصال امن برای SSL داریم، یکی اینکریپشن و یکی آتنتیکیشن، اینجا فقط اینکریپشن رو توضیح میدم، خودتون میتونید آتنتیکیشن از اینجا یا داکیومنت رسمی یادبگیرین (به بقیه داکیومنتام نگاه کنید، احتمالا براتون جالبه). برای ایجاد کلیدهای SSL باید با openssl آشنا باشید، البته دستورات مربوط به کلیدهارو داخل makefile گذاشتم میتونید با دستور make ssl کلیدهارو بدون دردسر جنریت کنید.اگر با Makefile آشنا نیستید باید بگم می‌تونید با دستور apt install make روی لینوکس یا با دستور brew install make روی مک نصبش کنید، حالا برای ساخت کلید فقط کافیه دستور make ssl بزنید، خودش به ترتیب دستورات لازم رو اجرا میکنه. (البته حسواستون باشه در روت پروژه باشید)حالا فایل سرور رو باز میکنیم و تابع main رو به این صورت تغییر میدیم:func main() {
   fmt.Println(&quot;Server is running...&quot;)

   // Make a listener
   lis, err := net.Listen(&quot;tcp&quot;, &quot;0.0.0.0:50051&quot;)
   if err != nil {
      log.Fatalf(&quot;Failed to listen: %v&quot;, err)
   }

   // SSL config
   certFile := &quot;../ssl/server.crt&quot;
   keyFile := &quot;../ssl/server.pem&quot;
   creds, sslErr := credentials.NewServerTLSFromFile&#40;certFile, keyFile&#41;
   if sslErr != nil {
      log.Fatalf(&quot;Faild loading certificates: %v&quot;, sslErr)
   }
   opts := grpc.Creds(creds)

   // Make a gRPC server
   grpcServer := grpc.NewServer(opts)
   calculatorpb.RegisterCalculatorServiceServer(grpcServer, &amp;server{})

   // Run the gRPC server
   if err := grpcServer.Serve(lis); err != nil {
      log.Fatalf(&quot;Failed to serve: %v&quot;, err)
   }
}در این قسمت کانفیگ ssl رو اضافه کردیم، کلیدهارو معرفی کردیم و یک آبجکت آپشن برای سرور درست کردیم. شاید بگید این کدها از کجا اومد، اگر به داکیومنت رسمی gRPC نگاه کنید این نمونه‌هارو اونجا میبینید.حالا تابع main کلاینت رو به این صورت تغییر میدیم:func main() {
   fmt.Println(&quot;Client is running...&quot;)

   // SSL config
   certFile := &quot;../ssl/ca.crt&quot;
   creds, sslErr := credentials.NewClientTLSFromFile&#40;certFile, &quot;api.example.com&quot;&#41;
   if sslErr != nil {
      log.Fatalf(&quot;Error while loading CA trust certifiate: %v&quot;, sslErr)
   }
   opts := grpc.WithTransportCredentials(creds)

   cc, err := grpc.Dial(&quot;localhost:50051&quot;, opts)
   if err != nil {
      log.Fatalf(&quot;could not connect to server: %v&quot;, err)
   }
   defer cc.Close()

   c := calculatorpb.NewCalculatorServiceClient(cc)

   doSum(c)
}میبینید اینجام به همون صورت ولی با کنفیگ کلاینت کلید SSL رو ست کردیم.تنها نکته اینه توی makefile یک متغییر به اسم  داشتیم که SERVER_CN بهش مقدار api.example.com دادیم و اینجا توی کلاینت هم باید همون مقدار رو برای کانفیگ ssl ست کنیم.کدهای این بخش رو از گیت‌هاب ببینید.‏gRPC Reflectionقبلا دیدم که برای اینکه کاربران‌مون بتونند به سرویس‌های ما وصل بشن نیاز دارن تا فایل‌های proto که نوشتیم رو داشته باشن، این برای پروداکشن مشکلی نداره چون میخواید مطمئن بشید که سرویس‌هارو به درستی صدا میکنید.ولی برای محیط دولوپمنت ممکنه فقط بخواید بدونید سرور چه API هایی داره یا میخواید خیلی سریع یک API رو صدا کنید و نتیجه رو ببینید بدون اینکه مجبور به کد زدن باشید، این قابلیت توسط gRPC رفلکشن در دسترستونه، با این قابلیت این امکان رو دارید بدون داشتن فایل پروتو ببینید سرور چه سرویس‌هایی داره و یکی یکی شون رو امتحان کنید.پیاده سازی رفلکشن خیلی سادس، میتونید نمونه رسمی شو از اینجا ببینید.func main() {
   fmt.Println(&quot;Server is running...&quot;)

   // Make a listener
   lis, err := net.Listen(&quot;tcp&quot;, &quot;0.0.0.0:50051&quot;)
   if err != nil {
      log.Fatalf(&quot;Failed to listen: %v&quot;, err)
   }

   // SSL config
   tls := false
   opts := []grpc.ServerOption{}
   if tls {
      certFile := &quot;../ssl/server.crt&quot;
      keyFile := &quot;../ssl/server.pem&quot;
      creds, sslErr := credentials.NewServerTLSFromFile&#40;certFile, keyFile&#41;
      if sslErr != nil {
         log.Fatalf(&quot;Faild loading certificates: %v&quot;, sslErr)
      }
      opts = append(opts, grpc.Creds(creds))
   }

   // Make a gRPC server
   grpcServer := grpc.NewServer(opts...)
   calculatorpb.RegisterCalculatorServiceServer(grpcServer, &amp;server{})

   // Register reflection service on gRPC server.
   reflection.Register(grpcServer)

   // Run the gRPC server
   if err := grpcServer.Serve(lis); err != nil {
      log.Fatalf(&quot;Failed to serve: %v&quot;, err)
   }
}همینطور که میبینید فقط با اضافه کردن یک خط (reflection.Register(grpcServer رفلکشن به سرور اضافه شد. ولی نکته اینه نمیشه با حالت ssl به رفلکشن وصل شد پس حالت خاموش کردن ssl رو اضافه کردیم.خب مرحله بعدی نصب evans هستش، سری به پیجش بزنید و برای سیستمتون نصبش کنید.حالا بعد از اجرای سرور می‌تونید با دستور evans -p 50051 -r به سرور وصل بشید، با دستور show package می‌تونید می‌تونید لیست پکیج‌هارو ببینید، با دستور package calculator وارد پکیج ماشین حساب میشیم، با دستور show service لیست سرویس‌ها رو میبینم و با دستور call Sum به API جمع دو عدد در ماشین حساب دسترسی داریم و میتونیم تستش کنیم.شاید کار کردن با ترمینال براتون سخته و دنبال ابزاری مثل پست‌من هستید، باید بگم همچین ابزاری به اسم bloomrpc وجود داره ولی از رفلکشن ساپورت نمیکنه و باید فایل‌های پروتو رو بهش بدید ولی خیلی سادس، در تصویر پایین میتونید محیطشو ببینید:خیلی خوب، فکر میکنم تا همینجا کافیه، بیشتر این تمرینات برای این آموزش ویدیویی بود، اگر لازم میدونید می‌تونید دانلودش کنید و خودتون ببینید همچنین برای جاوا هم آموزش درست کرده که تو سایت یودمی می‌تونید ببینید، من سعی کردم خلاصه‌ای ازش بگم تا درک مطالب آینده آسون تر بشه.</description>
                <category>عرفان</category>
                <author>عرفان</author>
                <pubDate>Sat, 15 Jun 2019 17:32:37 +0430</pubDate>
            </item>
                    <item>
                <title>پروتوباف (قسمت دوم)</title>
                <link>https://virgool.io/@erfun/%D9%BE%D8%B1%D9%88%D8%AA%D9%88%D8%A8%D8%A7%D9%81-%D9%82%D8%B3%D9%85%D8%AA-%D8%AF%D9%88%D9%85-cc7sre2bvnwb</link>
                <description>اگه قسمت اول پروتوباف نخوندین پیشنهاد میدم حتما اول قسمت اول رو بخونید: https://virgool.io/@erfun/protobuf-%DA%86%DB%8C%D9%87-%D9%88-%DA%86%D8%B7%D9%88%D8%B1-%DA%A9%D8%A7%D8%B1-%D9%85%DB%8C%DA%A9%D9%86%D9%87-pbgamxvyh285 آپدیت کردن فایل پروتوبیزینس شما رشد میکنه و شما باید API هارو آپدیت کنید، بعضی فیلدها تغییر می‌کنند،  بعضی فیلد‌ها اضافه  و بعضی حدف میشن.بیاید یه سناریو درنظر بگیریم، امروز موقع ثبت‌نام فقط نام و نام خانوادگی دریافت می‌کنید، ولی بعد یه مدت به شماره تلفن کاربران نیاز پیدا می‌کنید. ولی شما نمی‌تونید در یک لحظه فایل پروتو تمام اپلیکیشن‌هایی که دارن از سرویس شما استفاده می‌کنند آپدیت کنید.خوشبختانه پروتوبافر اینجاهم به دادمون میرسه تا بتونیم بدون دردسر به توسعه اپ ادامه بدیم، پروتوباف full compatibe هست، اگر با این مفهوم آشنا نیستید به عکس پایین نگاه کنید:در سناریو یک دیتا با فایل جدید نوشته میشه ولی با فایل قدیمی خونده میشه و مشکلی پیش نمیاد.در سناریو دوم دیتا با فایل قدیمی نوشته میشه ولی با فایل جدید خونده میشه که بازم مشکلی پیش نمیاد.ولی اینجا چندتا قانون برای آپدیت فایل پروتو داریم:هیچوقت نباید تگ فیلدهارو تغییر بدیم.شما می‌تونید فیلد جدید اضافه کنید، ولی اپلیکیشن با فایل پروتو قبلی اون فیلد‌هارو درنظر نمی‌گیره.اگر اپلیکیشن شما بخواد فیلدی رو از ریکوئست دریافت کنه ولی نتونه پیداش کنه، مقدار پیش‌فرض درنظر میگیره. (به همین دلیله باید مقدار دیفالت رو بشناسید و مدیریتشون کنید) همچنین فیلدها می‌تونند حذف بشن، ولی نباید دوباره تگ اون فیلد توی اون مسیج استفاده بشه. برای حذف می‌تونید به اول اسم فیلد OBSOLETE_ اضافه کنید یا تگ رو به reservet اضافه کنید.برای تغییر دیتاتایپ (برای مثال از int32 به int64) بهتره از داکیومنت اصلی کمک بگیرید، چون خیلی پیچیدس و بهتره خودتون بخونید. (به نظرم هیچوقت دیتاتایپ رو عوض نکنید، فقط قبلی رو حذف و یک جدید ایجاد کنید)اضافه کردن فیلد جدیدبرای اضافه کردن تگ جدید کافیه فیلدتون رو طبق معمول تعریف کنید و یک تگ جدید براش درنظر بگیرید. برای مثال:message MyMessage {
    int32 id = 1;
}به این صورت تغییر میدیم:message MyMessage {
     int32 id = 1; 
     string name = 2;
}اگر مسیج جدید به اپلیکشنی با فایل قدیمی ارسال بشه، اپلیکیشن قدیمی متوجه نمیشه تگ ۲ چیه، پس کلا درنظرش نمیگیره.حالا برعکس، اگر مسیج قدیمی به اپلیکیشنی با فایل جدید ارسال بشه، اپلیکیشن جدید تگ ۲ دریافت نمیکنه، پس مقدار name رو مقدار پیش‌فرض (استرینگ خالی) درنظر میگیره.مقادیر پیش‌فرض به ما اجازه میدن بدون آپدیت سرویس‌های وابسته فایل پروتو توسعه بدیم و فیلدهایی اضافه یا کم کنیم. تغییر نام فیلدتغییر نام یک فیلد خیلی خیلی سادس، فقط کافیه نام یک فیلد رو تغییر بدید، همین. برای مثال میخوای مقدار name رو به first_name تغییر بدیم:message MyMessage {
      int32 id = 1;
      string name = 2;
}فایل بالا رو به این صورت تغییر میدیم:message MyMessage {
       int32 id = 1;
       string first_name = 2; 
} تمام اپلیکیشن‌های قبلی به کارشون ادامه میدن، مشکلی پیش نمیاد و نیاز به آپدیت فایل پروتو بقیه سرویس‌ها ندارید. همونطور که قبلا گفتیم فقط تگ مهمه و اسم فیلدها موقع کدنویسی به کار میاد.حذف فیلدبرای حذف یک فیلد میتونید از داخل فایل حذفش کنید برای مثال فیلد name رو حذف میکنیم:message MyMessage {
        int32 id = 1;
        string first_name = 2;  
}و به این صورت حذف میکنیم:message MyMessage {
         int32 id = 1;
}و تمام، فیلد حذف شد، اگر کد شما نتونه فیلد موردنظر رو پیدا کنه مقدار پیش‌فرض براش درنظر میگیره، و اگر دیتای فایل قدیمی رو با مسیج جدید بخونیم، مقدار first_name درنظر گرفته نمیشه و حذف میشه.وقتی شما یک تگ رو حذف می‌کنید دیگه نباید هیچوقت از اون تگ و اسم فیلد استفاده کنید، به اصلاح میگن reserv کردن.برای رزرو کردن یک تگ به این صورت عمل میکنیم:message MyMessage {
         int32 id = 1;
         reserved 2;
         reserved &quot;first_name&quot;;
}دلیل انجام این کار اینه که یادمون باشه این تگ و اسم دیگه هیچوقت نباید داخل این مسیج استفاده بشن تا کانفلیکتی پیش نیاد.یا روش دیگه برای حذف یک فید به این صورته:message MyMessage {
         int32 id = 1;
         string OBSOLETE_first_name = 2;   
}این روش در عمل فرقی با روش قبلی نداره، هرکدوم که مایل هستید می‌تونید انجام بدید.رزرو فیلد‌هاشما میتونید تگ یا اسم فیلدهارو به دلایل مختلف رزرو کنید و حتی رنجی از تگ‌هارو رزرو کنید:message Foo {
    reserved 2, 5, 10 to 14;
    reserved &quot;Foo&quot;, &quot;Bar&quot;;
}ولی یادتون باشه نمی‌تونید تگ و اسم فیلد باهم میکس و رزرو کنید، حتما باید جدا جدا باشن.نکته مهم اینه هیچوقت و هیچوقت مقادیر رزرو رو حذف نکنید، برای مثال بعد چندماه یا چندسال نگید خب خیلی وقته از این رزروها میگذره و دیگه بهش نیازی ندارم پس بذار حذفش کنم، نه، شما همیشه بهشون نیاز دارید، همیشه ممکنه به باگ یا کانفلیکت بخورید.مقادیر عددیاحتمالا موقع نوشتن فایل پروتو گیج بشید که باید از کدوم یکی نوع مقدار استفاده کنید، برای مثال فقط برای اعداد گزینه‌های: int32, int64, uint32, uint64, sint32, sint64, fixed32, fixed64, sfixed32, sfixed64 می‌تونید انتخاب کنید.برای روشن‌تر شدن موضوع توصیه میکنم از اینجا به داکیومنت اصلی نگاهی بندازید، کمی اسکرول دان کنید جدولی میبینید که توضیح کاملی از تایپ موردنظر تو زبانی که کار می‌کنید داره. اگر بازهم متوجه نشدید سعی میکنم توضیح کوتاهی درموردش بدم.تمام مقادیر دو نوع ۳۲ بیت و ۶۴ بیت دارن.تمام نوع‌ها ممکنه بتونند اعداد منفی بگیرن یا نگیرن. (Signed/Unsigned)تمام این تایپ‌ها موقع کامپایل تبدیل به تایپ قابل فهم برای زبان‌های برنامه شما میشن، پس حتما از جدول این صفحه استفاده کنید.درک این تایپ‌ها کمک میکنه تا برنامه‌ای با پرفورمنس بالا بنویسید.تایپ int32 و sint32 میتونه مقادیر -۲،۱۴۷،۴۸۳،۶۴۸ تا ۲،۱۴۷،۴۸۳،۶۴۷ در خودش ذخیره کنهتایپ uint32 میتونه مقادیر ۰ تا ۴،۲۹۴،۹۶۷،۲۹۵ در خودش ذخیره کنه.تایپ int64 و sint64 میتونه مقادیر -۹،۲۲۳،۳۷۲،۰۳۶،۸۵۴،۷۷۵،۸۰۸ تا ۹،۲۲۳،۳۷۲،۰۳۶،۸۵۴،۷۷۵،۸۰۷ در خودش ذخیره کنه.تایپ uint64 میتونه مقادیر ۰ تا ۱۸،۴۴۶،۷۴۴،۰۷۳،۷۰۹،۵۵۱،۶۱۵ در خودش ذخیره کنه.بنابراین یادتون باشه تایپ‌های uint32 و uint64 یا به اصطلاح Unsigned نمی‌تونند مقادیر منفی در خودشون ذخیره کنند.تایپ‌های int32 و int64 به صورت رسمی اعداد منفی قبول نمی‌کنند. (در این تایپ اعداد منفی فضای بیشتری اشغال می‌کنند)تایپ‌های sint32 و sint64 اعداد منفی قبول می‌کنند. (با تکنیکی به اسم ZigZag اعداد منفی فضای کمی اشغال می‌کنند)تایپ fixed32 همیشه ۴ بایت اشغال می‌کنه، اگر عددتون بزرگتر از ۲۶۸،۴۳۵،۴۵۶ بود کاراییش بهتر از uint32 هست، بهتره از این تایپ استفاده کنید.تایپ fixed64 همیشه ۸ بایت اشتغال می‌کنه، اگر عددتون بزرگتر از ۷۲،۰۵۷،۵۹۴،۰۳۷،۹۲۷،۹۴۰ بود  کاراییش بهتر از uint64 هست، بهتره از این تایپ استفاده کنید.تایپ OneOfتایپ OneOf میگه فقط یکی از متغییرهایی که براش تعریف میکنیم می‌تونه مقدار داشته باشه.message MyMessage {
    int32 id = 1;
    oneof example_oneof {
        string my_string = 2;
        bool my_bool = 3;
    }
}در مسیج بالا داریم میگیم یه آیدی داریم و بعد از اون یکی از مقادیر my_string یا my_bool داریم، پس وقتی داریم این دو مقدار رو میخونیم فقط ممکنه یکی از اون‌ها مقداری روش ست شده باشه. و اما چند نکته:فیلدهای oneof نمیتونند repated باشن.توسعه oneof میتونه خیلی پیچیده باشه.در موقع خواندن مسیج oneof، همه فیلدها نال خواهد بود، بجز آخرین فیلدی که موقع ارسال درخواست ست کردید. پس اگر my_string و my_bool ست کنید فقط my_bool داری مقدار خواهد بود و my_string نال خواهد بود.تایپ mapدر این تایپ می‌تونید اطلاعات رو به صورت key/value تعریف کنید (مشابه جیسون). از تمامی تایپ‌ها غیر از float/double پشتیابی میکنه، برای مثال:message MyMessage {
    map&lt;int32, Profile&gt; profiles = 1;
}
message Profile {
    int32 id = 1;
    string name = 2;
    int32 phone_number = 3;
}در مثال بالا ما مجموعه‌ای از یوزرها به همراه پروفایلشون داریم، که کلید هر پروفایل یوزرآیدی و مقدارش مسیج دیگه‌ای به عنوان پروفایله. و اما چند نکته: فیلدهای map نمیتونند repated باشن.هیچگونه مرتب سازی روی مپ اتفاق نمیفته، چون مپ key/value هستش.تایپ timestampپروتوبافر مجموعه‌ای از تایپ‌های مخصوص خودش رو داره با عنوان well known types که در تمام زبانهای برنامه نویسی پشتیبانی میشن. اینجا می‌تونید لیست کامل این تایپ‌هارو ببینید.یکی از این تایپ‌ها timestamp هست، که فیلدهای ثانیه و نانوثانیه داره. قبل استفاده این نوع تایپ حتما باید ایمپورتش کنید، برای مثال:import &quot;google/protobuf/timestamp.proto&quot;;

message MyMessage {
  google.protobuf.Timestamp my_field = 1;
}آپشن‌هاآپشن‌ها زمان کامپایل به کار میان و برای هر زبان ویژگی خاصی رو اضافه می‌کنند، برای مثال میشه گفت در زمان تولید کد برای زبان گولنگ اسم پکیج گو رو فلان چیز ست کنه، از اونجایی که لیست طولانی داره و تعدادشون بالاست توصیه میکنم داکیومنت رو برای زبان مورد نظرتون بخونید، برای مثال:syntax = &quot;proto3&quot;;

package google.protobuf;

option csharp_namespace = &quot;Google.Protobuf.WellKnownTypes&quot;;
option cc_enable_arenas = true;
option go_package = &quot;github.com/golang/protobuf/ptypes/duration&quot;;
option java_package = &quot;com.google.protobuf&quot;;
option java_outer_classname = &quot;DurationProto&quot;;
option java_multiple_files = true;
option objc_class_prefix = &quot;GPB&quot;;سرویس‌هاخب میخوایم درمورد ویژگی اصلی پروتوبافر حرف بزنیم، در فایل پروتو بجز مسیج‌ها میتونید سرویس‌ها روهم تعریف کنید،  سرویس، اندپوینتی هست که کاربر (یا بقیه سرویس‌ها) میتونه بهش دسترسی داشته باشه، برای مثال:syntax = &quot;proto3&quot;;

message SearchRequest {
  int32 person_id = 1;
}

message SearchResponse {
  int32 person_id = 1;
  string person_name = 2;
}

service SearchService {
  rpc Search(SearchRequest) returns (SearchResponse);
}در این فایل یک سرویس سرچ تعریف کردیم و گفتیم انتظار داریم یک مسیج سرویس ریکوئست دریافت کنیم و در جواب یک مسیج سرچ ریسپانس برمیگردونیم، سادس درسته؟ در اصل این شیوه ساخت یک API در فایل پروتو هست، در اینجا به یک فریمورک نیاز داریم تا این سرویس رو هندل کنه و کدهاشو جنریت کنه، در پروتو ورژن ۳ از فریمورک gRPC گوگل کمک میگیره، ولی شما میتونید روی اینترنت فریمورک‌های دیگم پیدا کنید.خب اینم از یک مثال، فرض کنید شما یک سرور جاوا دارید (ولی میتونه ره زبان دیگه‌ای باشه، به جز php) که یک سرویسی رو ارائه میده، حالا هر کلاینتی با هر زبانی، برای مثال گولنگ، پایتون، جاوا و... میتونه با کمک فایلهای پروتو کدهای کلاینت gRPC جنریت کنه، و کلاینت یک ریکوئست پروتوبافر تولید میکنه برای سرور میفرسته، و سرور در پاسخ یک ریسپانس پروتوبافر ارسال میکنه. پس پروتوبافر اینجا یک شیوه انتقال دیتا توی شبکه‌اس که تمام زبان‌هارو ساپورت میکنه که همین نقطه قوتش شده.در پایان می‌تونید از اینجا با استایل گاید پروتوباف آشنا بشید.در قسمت بعدی درمورد فریمورک gRPC بیشتر حرف میزنیم و چند نمونه عملی اجرا میکنیم.</description>
                <category>عرفان</category>
                <author>عرفان</author>
                <pubDate>Sat, 11 May 2019 01:04:48 +0430</pubDate>
            </item>
                    <item>
                <title>آموزش صفر تا ۵۰ پروتوبافر</title>
                <link>https://virgool.io/@erfun/protobuf-%DA%86%DB%8C%D9%87-%D9%88-%DA%86%D8%B7%D9%88%D8%B1-%DA%A9%D8%A7%D8%B1-%D9%85%DB%8C%DA%A9%D9%86%D9%87-pbgamxvyh285</link>
                <description>اگر قبلا درمورد HTTP/2 نخوندید بهتره اول این مطلب رو بخونید.همچنین ازتون انتظار میره درمورد API و همچنین رست‌فول بدونید، اینجا میتونید بیشتر درموردشون بخونید.خیلی خب، حالا که درمورد HTTP/2 و انواع API میدونید، باید اینو متوجه شده باشید که استانداردهای فعلی با HTTP/2 سازگاری ندارن (یا بهتره بگم از ویژگیای HTTP/2 استفاده نمی‌کنند). برای مثال در استانداردهای گذشته کاربر یک استرینگ (در قالب جیسون، CSV ،XML یا...) به سمت سرور ارسال میکنه و سرور هم یک استرینگ به کاربر برمی‌گردونه، برای هر ریکوئست یک ارتباط TCP برقرار میشه و...اگر می‌خواید بدونید پروتوباف چیه بهتره اول انواع ساختار مرسوم دیتا چیه، در مرحله اول CSV وجود داشت که یک سری ستون تعریف میکردید و برای اون ستون ها ردیف اضافه می‌کردید.میشد راحت ساختش، راحت خوندش، راحت درکش کرد ولی نمیشد نوع دیتاهارو تعریف کرد، وقتی توی دیتاتون ویرگول داشته باشید کارِتون خیلی سخت میشه، نمی‌تونید مطمئن باشید دارید دیتارو توی ستون درست ذخیره می‌کنید یا نه.در مرحله بعد XML داشتیم که فکر نمیکنم دیگه هیچ‌جایی استفاده بشه پس توضیح نمیدم ولی توی وب خیلی کاربرد داشت تا اینکه جی‌سون معرفی شد.جی‌سون هر نوع دیتایی تو خودش جا میداد (آرایه، آبجکت، تو در تو)، تو اپلیکیشن‌های وب خیلی محبوب بود، با تمام زبان‌های برنامه نویسی سازگار بود، به راحتی توی شبکه انتقال داده میشد. ولی چندتایی مشکل داشت، برای مثال الگویی نداشت و توی هر ریکوئست میتونست شکل متفاوتی داشته باشه، چون کلید‌هارو هر دفعه تکرار میکنه حجم بزرگتری داشت، همچنین هیچ داکیومنت یا کامنتی همراشون ندارن.و اما پروتوباف (protocol buffers) در سال ۲۰۰۸ توسط گوگل ایجاد شد (که در اون تاریخ SPDY نام داشت)، یک استاندارده که بیشتر برای ارتباط داخلی (ارتباط سرویس‌ها با یکدیگر) استفاده میشه اما این روزها دارن تلاش میکنند که این استاندارد سمت کاربر هم قابل استفاده باشه که فعلا در اندروید امکان پذیره و نمونه‌هایی هم برای مرورگر آماده شده، به علت باینری بودن قابل استفاده در HTTP/2 است از همین جهت سرعت و پرفورمنس خیلی بهتری داره.نمونه‌ای از یک فایل پروتوباف  پروتوباف همچین شکلی داره، یک فایل تکست با پسوند proto که به سادگی برای انسان قابل خوندن و درکه، در خط اول ورژن پروتوباف رو مشخص میکنیم، در خط سه یک مسیج تعریف میکنیم که یک فیلد از جنس int با تگ ۱ و یک فیلد از جنس استرینگ با تگ ۲ و یک فیلد از جنس بولین با تگ ۳ داریم.مزیت‌های پروتوباف: تایپ دیتاها تعریف شدس، دیتاها بصورت اتوماتیک کمپرس میشن، تمام مسیج‌های ارسالی و دریافتی ساختار (schema) دارن (توی فایل proto تعریف میشن و بعدا توسط جنریتور کد مخصوص زبان شما جنریت میشه)، داکیومنت میتونه توی فایل proto نوشته بشه، دیتاها میتونه توی هر زبان برنامه نویسی‌ای خونده بشه، ساختار (schema) میتونه هرزمانی توسعه پیدا کنه، حدود ۷ برابر سریعتر از جیسونه، کدها بصورت اتوماتیک برای زبان شما جنریت میشه.و معایب پروتوباف: ممکنه کد جنریتورش بعضی از زبانها رو ساپورت نکنه، نمی‌تونید دیتاهارو موقع انتقال با تکست ادیتور باز کنید (چون باینتریه).تایپ‌ها:‌اعداد صحیح: می‌تونید از int32 و int64 برای اعداد صحیح استفاده کنید.اعداد اعشاری: می‌تونید از float32 یا float64 استفاده کنید.رشته: برای تعریف یک رشته میتونید از نوع string استفاده کنید، طول این متغیر خودکار تعین میشه، پس نگرانی بابت بزرگ بودن مقدار نداشته باشید، اینکد فایل باید UTF-8 یا 7-bit ASCII باشه.باینری: برای تعریف آرایه‌ای از بایت‌ها از bytes استفاده می‌کنیم، به شما بستگی داره چی چیزی میخواید توش ذخیره کنید، میتونه یک عکس کوچیک یا هر چیز باینری دیگه‌ای باشه.میتونید انواع تایپ‌های دیگه‌رو از داکیومنت اصلی بخونید. https://gist.github.com/ErFUN-KH/d660d1ae9ae95a0c8101f055fc49b02c تگ‌هادر پروتوبافر اسم فیلدها مهم نیستند (البته منظورمون بعد از بیلد هست، وگرنه موقع دولوپ خیلی هم مهمه!)، چیزی که مهمه تگ‌ها هستند، منظورمون همون عدادی هستند که در انتهای هر فیلد به ترتیب ۱ ۲ ۳ می‌نویسیم، در پروداکشن برای شناسایی فیلدها از اسمشون استفاده نمیشه بلکه از تگشون استفاده میشه، پس هر فیلد باید یک تگ منحصر به فرد داشته باشه، کوچیکترین تگی که میتونید استفاده کنید ۱ هست و بزرگترین ۵۳۶،۸۷۰،۹۱۱ هستش، اگر فکر میکنید این مقدار براتون کافی نیست احتمالا برنامه‌تون منطق اشتباهی داره، همچنین شما نمیتونید از تگهای ۱۹۰۰۰ تا ۱۹۹۹۹ استفاده کنید، این تگها توسط گوگل برای استفاده‌های خاص رزرو شدن.چیزی که لازمه بدونید تگ‌های ۱ تا ۱۵ فقط یک بایت فضا اشغال میکنند و تگ‌های ۱۶ تا ۲۰۴۷ دو بایت فضا اشغال می‌کنند.آرایه‌هاآرایه‌ها در پروتوبافر با عنوان repeated شناخته میشن، برای مثال شاید بخوایم برای پروفایلی که داشتیم چندین شماره تلفن درنظر بگیریم، پس فایل رو به این صورت تغییر میدیم: https://gist.github.com/ErFUN-KH/b702a01f149cc981e3e3d11744f7bae1 همینطور که میبینید فقط کافیه کلمه repeated قبل از تایپ فیلدمون اضافه کنیم.کامنتشما می‌تونید کامنتاتون به فایل پروتوباف اضافه کنید تا نیازی به نوشتن داکیومنت ضافه مثل سرویس‌های رست‌فول نباشید، شما فقط فایل‌های پروتو در اختیار بقیه دولوپرها قرار میدید و اونها سرویس مورد نظر رو کال می‌کنند. https://gist.github.com/ErFUN-KH/3c31c711685c924cef9423ef000f2859 همینطور که میبینید میشه کامنتهارو به ۲ صورت تک خطی یا چند خطی تعریف کرد، بهتون پیشنهاد میکنم حتما برای فایلاتون کامنت بنویسید تا تیم‌های دیگه برای توسعه گیج نشن.مقادیر پیش‌فرضچیزی که خیلی مهمه تا بدونید مقدار پیش فرض در پروتوبافه، تمام فیلدها مقدار پیش‌فرض دارن، برای بولین مقدار false هستش، برای نامبر عدد ۰ هستش،  برای استرینگ یک رشته خالی، برای بایت یک رشته بایت خالی، برای ای‌نام (enum) اولین مقدار لیست، برای آرایه یک آرایه خالی.تمام فیلدهایی که بعد از اجرای برنامه مقدار دهی نشن، شناخته نشن و... مقدار پیش‌فرض رو میگیرن.ای‌نام - enumاگر با ای‌نام آشنا نیستید باید بگم یک لیست از مقدایره، همونطور که بولین یک لیست دوتایی از true و false هستش، ای‌نام میتونه یک لیست چندتایی از هر تایپی باشه. اگر فراموش کردید باید دوباره بگم مقدار اول ای‌نام میشه مقدار پیش‌فرض، نباید اینو فراموش کنید.ای‌نام باید از تگ ۰ شروع بشه که مقدار پیش‌فرض هستش.پس میریم که رنگ چشم کاربر توسط ای‌نام تعریف کنیم: https://gist.github.com/ErFUN-KH/594834635e9c056863422a6c5e90076a فکر میکنم کد خیلی سادس و توضیح بیشتری لازم نداره، یک ای‌نام تعریف کردیم و در پایین ازش برای رنگ چشم استفاده کردیم.مسیج‌های تو در توعلاوه بر ای‌نام شما میتونید از مسیج‌های دیگه داخل مسیج اصلی‌تون استفاده کنید، اینکار خیلی سادس، تو نمونه پایین تاریخ تولد به پروفایل کاربر اضافه میکنیم: https://gist.github.com/ErFUN-KH/ec9a0c1685ffafe00a5093b7b7235f9c تو خط ۳۴ میبینم تاریخ تولد رو تعریف کردیم که از جنس Date هست، که خود دیت یک مسیجه که شامل سال، ماه و روز از جنس int هستش که در خط ۳۷ تعریفش کردیم.همیشه می‌تونید داخل مسیجاتون یک مسیج دیگه رو استفاده کنید.ایمپورت کردن فایل‌های protoشاید شما کلی فایل پروتو داشته باشید و نخواید مسیج Date همه جا تعریف کنید، بلکه توی یک فایل تعریف کنید و جاهای دیگه ایمپورت کنید، برای این کار به این صورت اقدام میکنید: https://gist.github.com/ErFUN-KH/34b750e736cfcc330f391c760d8fdeb7 همینطور که توی خط ۴ فایل پروفایل میبینید فایل date.proto ایمپورت کردیم، تنها نکته‌ای که هست شما باید آدرس کامل فایل رو از روت پروژه وارد کنید، شاید فایلها کنار هم باشن ولی نمیتونید فقط اسم فایل رو وارد کنید، در اینصورت موقع جنریت کردن کد حتما به خطا می‌خورید.پکیج‌هاخیلی مهمه برای فایل‌های پروتو پیکج تعریف کنید، وقتی کد برای زبان خودتون جنریت کنید این پکیج‌ها کاربرد دارن، همچنین از کانفلیکت بین اسم مسیج‌ها جلوگیری میکنه، پیکج‌ها تو تمام زبان‌های پشتیبانی میشن. https://gist.github.com/ErFUN-KH/8f4027095a71dedbf5674194d9d32bcd تو کد بالا برای هر فایل یک package name تعریف کردیم، خیلی ساده بود، تنها نکته اینه وقتی تو فایل پروفایل داشتیم Date تعریف میکردیم اول پیکج‌نیم رو نوشتیم و بعد...فکر میکنم ۵۰٪ مسئله روشن شده، توی پست بعدی ۵۰٪ دیگه رو توضیح میدم. https://virgool.io/@erfun/%D9%BE%D8%B1%D9%88%D8%AA%D9%88%D8%A8%D8%A7%D9%81-%D9%82%D8%B3%D9%85%D8%AA-%D8%AF%D9%88%D9%85-cc7sre2bvnwb </description>
                <category>عرفان</category>
                <author>عرفان</author>
                <pubDate>Fri, 22 Mar 2019 18:33:12 +0430</pubDate>
            </item>
                    <item>
                <title>HTTP/2 چیه و چطور کار میکنه؟</title>
                <link>https://virgool.io/@erfun/http2-%DA%86%DB%8C%D9%87-%D9%88-%DA%86%D8%B7%D9%88%D8%B1-%DA%A9%D8%A7%D8%B1-%D9%85%DB%8C%DA%A9%D9%86%D9%87-gnmgbedbdc4a</link>
                <description>اگر برنامه‌نویس هستید احتمالا درمورد HTTP/2 شنیدید، فکر نمیکردم چیز مهمی باشه و تاثیری روی کار من داشته باشه تا وقتی که به قضیه مایکروسرویس‌ها رسیدم، اونجا HTTP/2 نقش مهمی داره و اگر درست درکش نکرده باشید نمیتونید از خیلی چیزا سر دربیارید، البته درنظر داشته باشید حداقل تو کیس ما لازم نیست خیلی دیپ بشیم و فقط درک نحوه کارکردش میتونه کارمون راه بندازه.اول از همه انتظار دارم ازتون وارد این سایت بشید و روی دکمه click to load again بزنید، در سمت راست تصویر با HTTP/1 لود میشه و در سمت چپ با HTTP/2، از همینجا میشه به تفاوت سرعتشون پی‌برد، خب حالا چطور اینقدر سرعتش بیشتر شده؟HTTP/1.1 چطور کار میکنه؟قبل اینکه بخوایم بگیم چرا اینقدر سرعت HTTP/2 بیشتره باید بدونم HTTP/1 چیه و چطور کار میکنه، HTTP/1.1 درسال ۱۹۹۷ معرفی شد و در اون زمان خیلی موفق بود، HTTP/1.1 برای هر ریکوئست یک کانکشن TCP باز میکنه،  همینطور هدرهای هر ریکوئست به صورت تکست خام ارسال میشن و فشرده سازی روشون اعمال نمیشه، برای هر ریکوئست یک ریسپانس ارسال میشه (سرور پوش وجود نداره)، همینطور فقط دو متدو براش وجود داره، GET برای دریافت دیتا و POST برای ارسال دیتا. (شاید بگید متدهای دیگه‌ای مثل PATCH، PUT, DELETE و... وجود دارند که باید بگم تفاوتی با متد POST ندارن و به طور اصلی فقط همین دو متد وجود دارن)خب حالا در نظر بگیرید که هر وبسایت به صورت متوسط از ۸۰ فایل متفاوت تشکیل شده (عکس‌ها، CSS، JS و...) و در هر ریکويست برای این فایلها تمام هدرهای موجود رو ارسال میکنه (بدون فشرده سازی) و برای هر ریکوئست یک کانکشن TCP باز میکنه.در مثال بالا کاربر درخواست میده فایل HTML یک سایت دریافت کنه و سرور پاسخ میده، کاربر دوباره یک کانکشن TCP دیگه باز میکنه برای فایل CSS و سرور دوباره پاسخ میده، این کار برای هر سایت حدود ۸۰ بار انجام میشه و این اتفاقیه که برای لود تصویر با HTTP/1.1 در این سایت میفته.HTTP/2 چطور کار میکنه؟و اما HTTP/2 در سال ۲۰۱۵ معرفی شد، البته از خیلی سال پیش توسط گوگل و به اسم SPDY تست شده بود، HTTP/2 از قابلیت مولتی‌پلکسینگ (multiplexing) پشتیبانی میکنه، یعنی کاربر و سرور میتونند در یک ارتباط TCP چندین مسیج ارسال کنند، همچنین در HTTP/2 قابلیت سرور پوش داریم، یعنی کاربر فقط درخواست یک فایل میده و سرور میتونه چندین فایل ارسال کنه، برای مثال کاربر درخواست فایل HTML میده و سرور فایل HTML به علاوه CSS, JS, Image و... ارسال میکنه، همچنین HTTP/2 قابلیت فشرده سازی هدرهارو داره،  HTTP/2 باینری است، این تغییر بزرگیه، در HTTP/1.1 خیلی راحت میتونستید دیباگ کنید ولی اینجا به علت باینری بودن کار سخت میشه، HTTP/2 امنه، SSL الزامی نیست ولی به صورت پیش‌فرض پیشنهاد میشه.HTTP/2</description>
                <category>عرفان</category>
                <author>عرفان</author>
                <pubDate>Fri, 22 Mar 2019 12:25:00 +0430</pubDate>
            </item>
                    <item>
                <title>ساخت پروژه با معماری مایکروسرویس، زبان گولنگ، اندپوینت رست، کوبرنتیز و... (قسمت سوم)</title>
                <link>https://virgool.io/golangpub/%D8%B3%D8%A7%D8%AE%D8%AA-%D9%BE%D8%B1%D9%88%DA%98%D9%87-%D8%A8%D8%A7-%D9%85%D8%B9%D9%85%D8%A7%D8%B1%DB%8C-%D9%85%D8%A7%DB%8C%DA%A9%D8%B1%D9%88%D8%B3%D8%B1%D9%88%DB%8C%D8%B3-%D8%B2%D8%A8%D8%A7%D9%86-%DA%AF%D9%88%D9%84%D9%86%DA%AF-%D8%A7%D9%86%D8%AF%D9%BE%D9%88%DB%8C%D9%86%D8%AA-%D8%B1%D8%B3%D8%AA-%DA%A9%D9%88%D8%A8%D8%B1%D9%86%D8%AA%DB%8C%D8%B2-%D9%88-%D9%82%D8%B3%D9%85%D8%AA-%D8%B3%D9%88%D9%85-vjapbe9ditwm</link>
                <description>این مطلب ادامه قسمت دوم است. در قسمت قبل اندپوینت رست به سرویس gRPC اضافه کردیم، تو این قسمت میخوایم middleware به سرویس gRPC و REST اضافه کنیم.می‌تونید سورس کد قسمت سوم از اینجا دریافت کنید.قدم اول: اضافه کردن Uber zap loggerقدم اول اینه اوبر زب با لاگ استاندارد گولنگ عوض کنیم، چون فریمورک میدلور gRPC زب رو پشتیبانی میکنه.فایل  logger.go در پوشه “pkg/logger” همراه با محتوای زیر ایجاد کنید: https://gist.github.com/amsokol/70ed47cd4c32bc658f8c726e5f51f94e فایل “pkg/cmd/server.go” برای اضافه کردن زب آپدیت کنید: https://gist.github.com/amsokol/a655142929103438214e8d75c8766146 لاگ استاندارد در فایل “pkg/protocol/grpc/server.go” رو با زب عوض کنید، تغییرات رو از اینجا ببینید.لاگ استاندارد در فایل “pkg/protocol/rest/server.go” رو با زب عوض کنید، تغییرات رو از اینجا ببینید.قدم دوم: اضافه کردن میدلور logging/tracing به سرویس gRPCاین لایبرری برای اضافه کردن میدلور به gRPC عالیه: https://github.com/grpc-ecosystem/go-grpc-middleware میخوایم اینو برای logging/tracing استفاده کنیم.فایل  logger.go در پوشه “pkg/protocol/grpc/middleware”  همراه با محتوای پایین ایجاد کنید: https://gist.github.com/amsokol/f41dc65c68eba00fcf66be616adb019e فایل “pkg/protocol/grpc/server.go” با محتوای پایین جهت اضافه کردن logging/tracing به سرور gRPC عوض کنید: https://gist.github.com/amsokol/0c8b5fcee7c725f0e15f4eef5dfcf80c ساختار پروژه‌تون باید شبیه به این شده باشه:خب امتحان کنیم سیستم لاگ‌مون چطور کار می‌کنه.سرور gRPC با لاگ لول دیباگ (-log-level=-1) اجرا می‌کنیم:cd cmd/server 
go build . 
server.exe -grpc-port=9090 -http-port=8080 -db-host=&lt;HOST&gt;:3306 -db-user=&lt;USER&gt; -db-password=&lt;PASSWORD&gt; -db-schema=&lt;SCHEMA&gt; -log-level=-1 -log-time-format=2006-01-02T15:04:05.999999999Z07:00یه ترمینال دیگه باز میکنم تا سرویس گیرنده gRPC اجرا کنیم:cd cmd/client-rest 
go build . 
client-rest.exe -server=http://localhost:8080برگردیم به سرور gRPC و به خروجی ترمینال نگاه میکنیم، اگه چیزی شبیه این بود یعنی همه چی به درستی کار میکنه:API server listening at: 127.0.0.1:2345
{&quot;level&quot;:&quot;info&quot;,&quot;ts&quot;:&quot;2018-09-16T09:54:16.8474966+03:00&quot;,&quot;msg&quot;:&quot;starting HTTP/REST gateway...&quot;}
{&quot;level&quot;:&quot;info&quot;,&quot;ts&quot;:&quot;2018-09-16T09:54:16.8484985+03:00&quot;,&quot;msg&quot;:&quot;starting gRPC server...&quot;}
{&quot;level&quot;:&quot;info&quot;,&quot;ts&quot;:&quot;2018-09-16T09:54:16.8554788+03:00&quot;,&quot;msg&quot;:&quot;pickfirstBalancer: HandleSubConnStateChange: 0xc00016c060, READY&quot;,&quot;system&quot;:&quot;grpc&quot;,&quot;grpc_log&quot;:true}
{&quot;level&quot;:&quot;debug&quot;,&quot;ts&quot;:&quot;2018-09-16T09:54:28.5287447+03:00&quot;,&quot;msg&quot;:&quot;finished unary call with code OK&quot;,&quot;peer.address&quot;:&quot;[::1]:49838&quot;,&quot;grpc.start_time&quot;:&quot;2018-09-16T09:54:28+03:00&quot;,&quot;system&quot;:&quot;grpc&quot;,&quot;span.kind&quot;:&quot;server&quot;,&quot;grpc.service&quot;:&quot;v1.ToDoService&quot;,&quot;grpc.method&quot;:&quot;Create&quot;,&quot;peer.address&quot;:&quot;[::1]:49838&quot;,&quot;grpc.code&quot;:&quot;OK&quot;,&quot;grpc.time_ms&quot;:99.84400177001953}
{&quot;level&quot;:&quot;debug&quot;,&quot;ts&quot;:&quot;2018-09-16T09:54:28.6258332+03:00&quot;,&quot;msg&quot;:&quot;finished unary call with code OK&quot;,&quot;peer.address&quot;:&quot;[::1]:49838&quot;,&quot;grpc.start_time&quot;:&quot;2018-09-16T09:54:28+03:00&quot;,&quot;system&quot;:&quot;grpc&quot;,&quot;span.kind&quot;:&quot;server&quot;,&quot;grpc.service&quot;:&quot;v1.ToDoService&quot;,&quot;grpc.method&quot;:&quot;Read&quot;,&quot;peer.address&quot;:&quot;[::1]:49838&quot;,&quot;grpc.code&quot;:&quot;OK&quot;,&quot;grpc.time_ms&quot;:38.01499938964844}
{&quot;level&quot;:&quot;debug&quot;,&quot;ts&quot;:&quot;2018-09-16T09:54:28.6695219+03:00&quot;,&quot;msg&quot;:&quot;finished unary call with code OK&quot;,&quot;peer.address&quot;:&quot;[::1]:49838&quot;,&quot;grpc.start_time&quot;:&quot;2018-09-16T09:54:28+03:00&quot;,&quot;system&quot;:&quot;grpc&quot;,&quot;span.kind&quot;:&quot;server&quot;,&quot;grpc.service&quot;:&quot;v1.ToDoService&quot;,&quot;grpc.method&quot;:&quot;Update&quot;,&quot;peer.address&quot;:&quot;[::1]:49838&quot;,&quot;grpc.code&quot;:&quot;OK&quot;,&quot;grpc.time_ms&quot;:42.224998474121094}
{&quot;level&quot;:&quot;debug&quot;,&quot;ts&quot;:&quot;2018-09-16T09:54:28.6873289+03:00&quot;,&quot;msg&quot;:&quot;finished unary call with code OK&quot;,&quot;peer.address&quot;:&quot;[::1]:49838&quot;,&quot;grpc.start_time&quot;:&quot;2018-09-16T09:54:28+03:00&quot;,&quot;system&quot;:&quot;grpc&quot;,&quot;span.kind&quot;:&quot;server&quot;,&quot;grpc.service&quot;:&quot;v1.ToDoService&quot;,&quot;grpc.method&quot;:&quot;ReadAll&quot;,&quot;peer.address&quot;:&quot;[::1]:49838&quot;,&quot;grpc.code&quot;:&quot;OK&quot;,&quot;grpc.time_ms&quot;:16.808000564575195}
{&quot;level&quot;:&quot;debug&quot;,&quot;ts&quot;:&quot;2018-09-16T09:54:28.7319168+03:00&quot;,&quot;msg&quot;:&quot;finished unary call with code OK&quot;,&quot;peer.address&quot;:&quot;[::1]:49838&quot;,&quot;grpc.start_time&quot;:&quot;2018-09-16T09:54:28+03:00&quot;,&quot;system&quot;:&quot;grpc&quot;,&quot;span.kind&quot;:&quot;server&quot;,&quot;grpc.service&quot;:&quot;v1.ToDoService&quot;,&quot;grpc.method&quot;:&quot;Delete&quot;,&quot;peer.address&quot;:&quot;[::1]:49838&quot;,&quot;grpc.code&quot;:&quot;OK&quot;,&quot;grpc.time_ms&quot;:41.12799835205078}قدم سوم: اضافه کردن میدلور به رستمی‌خوایم ۲ تا میدلور به رست اضافه کنیم:Request-IDLogging/Tracingمیدلور Request-ID  یک آیدی یونیک به ریکوئست HTTP اضافه میکنه. میدلور Logging/Tracing در قسمت بررسی لاگ‌های سرور به ما کمک میکنه تا ریکوئست‌های هر کاربر را از دیگر کاربران متمایز کنیم و مورد بررسی قرار بدیم.فایل request-id.go در پوشه  “pkg/protocol/rest/middleware” همراه با محتوای زیر ایجاد کنید: https://gist.github.com/amsokol/249c338c73a8cfac7cd9bba80f755e17 حالا فایل logger.go در پوشه “pkg/protocol/rest/middleware” همراه با محتوای زیر ایجاد کنید: https://gist.github.com/amsokol/ed20b0e79cb02c41945575071f64ef94 محتوای فایل  با “pkg/protocol/rest/server.go”محتوای زیر جایگزین کنید: https://gist.github.com/amsokol/bfcce2cdc0dcfc59b3fac0358556e924 ساختار پروژه باید به شکل زیر باشد:بریم چک کنیم کدها چطور کار می‌کنند.سرور gRPC با لاگ لول (-log-level=-1) اجرا کنید:cd cmd/server 
go build . 
server.exe -grpc-port=9090 -http-port=8080 -db-host=&lt;HOST&gt;:3306 -db-user=&lt;USER&gt; -db-password=&lt;PASSWORD&gt; -db-schema=&lt;SCHEMA&gt; -log-level=-1 -log-time-format=2006-01-02T15:04:05.999999999Z07:00یه ترمینال دیگه باز کنید و کلاینت rest اونجا اجرا کمید:cd cmd/client-rest 
go build . 
client-rest.exe -server=http://localhost:8080برگردید به تریمنالی که توش سرور gRPC اجرا کرده بودید، خروجی باید همچین چیزی باشه:API server listening at: 127.0.0.1:2345
{&quot;level&quot;:&quot;info&quot;,&quot;ts&quot;:&quot;2018-09-16T10:28:30.0875991+03:00&quot;,&quot;msg&quot;:&quot;starting HTTP/REST gateway...&quot;}
{&quot;level&quot;:&quot;info&quot;,&quot;ts&quot;:&quot;2018-09-16T10:28:30.0886057+03:00&quot;,&quot;msg&quot;:&quot;starting gRPC server...&quot;}
{&quot;level&quot;:&quot;info&quot;,&quot;ts&quot;:&quot;2018-09-16T10:28:30.0936242+03:00&quot;,&quot;msg&quot;:&quot;pickfirstBalancer: HandleSubConnStateChange: 0xc000052070, READY&quot;,&quot;system&quot;:&quot;grpc&quot;,&quot;grpc_log&quot;:true}
{&quot;level&quot;:&quot;debug&quot;,&quot;ts&quot;:&quot;2018-09-16T10:28:42.3320283+03:00&quot;,&quot;msg&quot;:&quot;request started&quot;,&quot;request-id&quot;:&quot;PC/5P2xo0mYah-000001&quot;,&quot;http-scheme&quot;:&quot;http&quot;,&quot;http-proto&quot;:&quot;HTTP/1.1&quot;,&quot;http-method&quot;:&quot;POST&quot;,&quot;remote-addr&quot;:&quot;[::1]:50181&quot;,&quot;user-agent&quot;:&quot;Go-http-client/1.1&quot;,&quot;uri&quot;:&quot;http://localhost:8080/v1/todo&quot;}
{&quot;level&quot;:&quot;debug&quot;,&quot;ts&quot;:&quot;2018-09-16T10:28:42.4345359+03:00&quot;,&quot;msg&quot;:&quot;finished unary call with code OK&quot;,&quot;peer.address&quot;:&quot;[::1]:50178&quot;,&quot;grpc.start_time&quot;:&quot;2018-09-16T10:28:42+03:00&quot;,&quot;system&quot;:&quot;grpc&quot;,&quot;span.kind&quot;:&quot;server&quot;,&quot;grpc.service&quot;:&quot;v1.ToDoService&quot;,&quot;grpc.method&quot;:&quot;Create&quot;,&quot;peer.address&quot;:&quot;[::1]:50178&quot;,&quot;grpc.code&quot;:&quot;OK&quot;,&quot;grpc.time_ms&quot;:100.51200103759766}
{&quot;level&quot;:&quot;debug&quot;,&quot;ts&quot;:&quot;2018-09-16T10:28:42.4345359+03:00&quot;,&quot;msg&quot;:&quot;request completed&quot;,&quot;request-id&quot;:&quot;PC/5P2xo0mYah-000001&quot;,&quot;http-scheme&quot;:&quot;http&quot;,&quot;http-proto&quot;:&quot;HTTP/1.1&quot;,&quot;http-method&quot;:&quot;POST&quot;,&quot;remote-addr&quot;:&quot;[::1]:50181&quot;,&quot;user-agent&quot;:&quot;Go-http-client/1.1&quot;,&quot;uri&quot;:&quot;http://localhost:8080/v1/todo&quot;,&quot;elapsed-ms&quot;:102.5076}
{&quot;level&quot;:&quot;debug&quot;,&quot;ts&quot;:&quot;2018-09-16T10:28:42.5072257+03:00&quot;,&quot;msg&quot;:&quot;request started&quot;,&quot;request-id&quot;:&quot;PC/5P2xo0mYah-000002&quot;,&quot;http-scheme&quot;:&quot;http&quot;,&quot;http-proto&quot;:&quot;HTTP/1.1&quot;,&quot;http-method&quot;:&quot;GET&quot;,&quot;remote-addr&quot;:&quot;[::1]:50181&quot;,&quot;user-agent&quot;:&quot;Go-http-client/1.1&quot;,&quot;uri&quot;:&quot;http://localhost:8080/v1/todo/29&quot;}
{&quot;level&quot;:&quot;debug&quot;,&quot;ts&quot;:&quot;2018-09-16T10:28:42.5457395+03:00&quot;,&quot;msg&quot;:&quot;finished unary call with code OK&quot;,&quot;peer.address&quot;:&quot;[::1]:50178&quot;,&quot;grpc.start_time&quot;:&quot;2018-09-16T10:28:42+03:00&quot;,&quot;system&quot;:&quot;grpc&quot;,&quot;span.kind&quot;:&quot;server&quot;,&quot;grpc.service&quot;:&quot;v1.ToDoService&quot;,&quot;grpc.method&quot;:&quot;Read&quot;,&quot;peer.address&quot;:&quot;[::1]:50178&quot;,&quot;grpc.code&quot;:&quot;OK&quot;,&quot;grpc.time_ms&quot;:37.51100158691406}
{&quot;level&quot;:&quot;debug&quot;,&quot;ts&quot;:&quot;2018-09-16T10:28:42.5457395+03:00&quot;,&quot;msg&quot;:&quot;request completed&quot;,&quot;request-id&quot;:&quot;PC/5P2xo0mYah-000002&quot;,&quot;http-scheme&quot;:&quot;http&quot;,&quot;http-proto&quot;:&quot;HTTP/1.1&quot;,&quot;http-method&quot;:&quot;GET&quot;,&quot;remote-addr&quot;:&quot;[::1]:50181&quot;,&quot;user-agent&quot;:&quot;Go-http-client/1.1&quot;,&quot;uri&quot;:&quot;http://localhost:8080/v1/todo/29&quot;,&quot;elapsed-ms&quot;:38.5138}
{&quot;level&quot;:&quot;debug&quot;,&quot;ts&quot;:&quot;2018-09-16T10:28:42.5467358+03:00&quot;,&quot;msg&quot;:&quot;request started&quot;,&quot;request-id&quot;:&quot;PC/5P2xo0mYah-000003&quot;,&quot;http-scheme&quot;:&quot;http&quot;,&quot;http-proto&quot;:&quot;HTTP/1.1&quot;,&quot;http-method&quot;:&quot;PUT&quot;,&quot;remote-addr&quot;:&quot;[::1]:50181&quot;,&quot;user-agent&quot;:&quot;Go-http-client/1.1&quot;,&quot;uri&quot;:&quot;http://localhost:8080/v1/todo/29&quot;}
{&quot;level&quot;:&quot;debug&quot;,&quot;ts&quot;:&quot;2018-09-16T10:28:42.590229+03:00&quot;,&quot;msg&quot;:&quot;finished unary call with code OK&quot;,&quot;peer.address&quot;:&quot;[::1]:50178&quot;,&quot;grpc.start_time&quot;:&quot;2018-09-16T10:28:42+03:00&quot;,&quot;system&quot;:&quot;grpc&quot;,&quot;span.kind&quot;:&quot;server&quot;,&quot;grpc.service&quot;:&quot;v1.ToDoService&quot;,&quot;grpc.method&quot;:&quot;Update&quot;,&quot;peer.address&quot;:&quot;[::1]:50178&quot;,&quot;grpc.code&quot;:&quot;OK&quot;,&quot;grpc.time_ms&quot;:42.492000579833984}
{&quot;level&quot;:&quot;debug&quot;,&quot;ts&quot;:&quot;2018-09-16T10:28:42.590229+03:00&quot;,&quot;msg&quot;:&quot;request completed&quot;,&quot;request-id&quot;:&quot;PC/5P2xo0mYah-000003&quot;,&quot;http-scheme&quot;:&quot;http&quot;,&quot;http-proto&quot;:&quot;HTTP/1.1&quot;,&quot;http-method&quot;:&quot;PUT&quot;,&quot;remote-addr&quot;:&quot;[::1]:50181&quot;,&quot;user-agent&quot;:&quot;Go-http-client/1.1&quot;,&quot;uri&quot;:&quot;http://localhost:8080/v1/todo/29&quot;,&quot;elapsed-ms&quot;:43.4932}
{&quot;level&quot;:&quot;debug&quot;,&quot;ts&quot;:&quot;2018-09-16T10:28:42.5913265+03:00&quot;,&quot;msg&quot;:&quot;request started&quot;,&quot;request-id&quot;:&quot;PC/5P2xo0mYah-000004&quot;,&quot;http-scheme&quot;:&quot;http&quot;,&quot;http-proto&quot;:&quot;HTTP/1.1&quot;,&quot;http-method&quot;:&quot;GET&quot;,&quot;remote-addr&quot;:&quot;[::1]:50181&quot;,&quot;user-agent&quot;:&quot;Go-http-client/1.1&quot;,&quot;uri&quot;:&quot;http://localhost:8080/v1/todo/all&quot;}
{&quot;level&quot;:&quot;debug&quot;,&quot;ts&quot;:&quot;2018-09-16T10:28:42.6090751+03:00&quot;,&quot;msg&quot;:&quot;finished unary call with code OK&quot;,&quot;peer.address&quot;:&quot;[::1]:50178&quot;,&quot;grpc.start_time&quot;:&quot;2018-09-16T10:28:42+03:00&quot;,&quot;system&quot;:&quot;grpc&quot;,&quot;span.kind&quot;:&quot;server&quot;,&quot;grpc.service&quot;:&quot;v1.ToDoService&quot;,&quot;grpc.method&quot;:&quot;ReadAll&quot;,&quot;peer.address&quot;:&quot;[::1]:50178&quot;,&quot;grpc.code&quot;:&quot;OK&quot;,&quot;grpc.time_ms&quot;:17.74799919128418}
{&quot;level&quot;:&quot;debug&quot;,&quot;ts&quot;:&quot;2018-09-16T10:28:42.6111404+03:00&quot;,&quot;msg&quot;:&quot;request completed&quot;,&quot;request-id&quot;:&quot;PC/5P2xo0mYah-000004&quot;,&quot;http-scheme&quot;:&quot;http&quot;,&quot;http-proto&quot;:&quot;HTTP/1.1&quot;,&quot;http-method&quot;:&quot;GET&quot;,&quot;remote-addr&quot;:&quot;[::1]:50181&quot;,&quot;user-agent&quot;:&quot;Go-http-client/1.1&quot;,&quot;uri&quot;:&quot;http://localhost:8080/v1/todo/all&quot;,&quot;elapsed-ms&quot;:19.8139}
{&quot;level&quot;:&quot;debug&quot;,&quot;ts&quot;:&quot;2018-09-16T10:28:42.6121388+03:00&quot;,&quot;msg&quot;:&quot;request started&quot;,&quot;request-id&quot;:&quot;PC/5P2xo0mYah-000005&quot;,&quot;http-scheme&quot;:&quot;http&quot;,&quot;http-proto&quot;:&quot;HTTP/1.1&quot;,&quot;http-method&quot;:&quot;DELETE&quot;,&quot;remote-addr&quot;:&quot;[::1]:50181&quot;,&quot;user-agent&quot;:&quot;Go-http-client/1.1&quot;,&quot;uri&quot;:&quot;http://localhost:8080/v1/todo/29&quot;}
{&quot;level&quot;:&quot;debug&quot;,&quot;ts&quot;:&quot;2018-09-16T10:28:42.6541939+03:00&quot;,&quot;msg&quot;:&quot;finished unary call with code OK&quot;,&quot;peer.address&quot;:&quot;[::1]:50178&quot;,&quot;grpc.start_time&quot;:&quot;2018-09-16T10:28:42+03:00&quot;,&quot;system&quot;:&quot;grpc&quot;,&quot;span.kind&quot;:&quot;server&quot;,&quot;grpc.service&quot;:&quot;v1.ToDoService&quot;,&quot;grpc.method&quot;:&quot;Delete&quot;,&quot;peer.address&quot;:&quot;[::1]:50178&quot;,&quot;grpc.code&quot;:&quot;OK&quot;,&quot;grpc.time_ms&quot;:42.05500030517578}
{&quot;level&quot;:&quot;debug&quot;,&quot;ts&quot;:&quot;2018-09-16T10:28:42.6541939+03:00&quot;,&quot;msg&quot;:&quot;request completed&quot;,&quot;request-id&quot;:&quot;PC/5P2xo0mYah-000005&quot;,&quot;http-scheme&quot;:&quot;http&quot;,&quot;http-proto&quot;:&quot;HTTP/1.1&quot;,&quot;http-method&quot;:&quot;DELETE&quot;,&quot;remote-addr&quot;:&quot;[::1]:50181&quot;,&quot;user-agent&quot;:&quot;Go-http-client/1.1&quot;,&quot;uri&quot;:&quot;http://localhost:8080/v1/todo/29&quot;,&quot;elapsed-ms&quot;:42.055ghwioخلاصه قسمت سومتمام این برای قسمت سوم بود. ما میدلور برای هردوسرویس gRPC و REST اضافه کردیم.سورس کد قسمت سوم از اینجا در دسترس شماست.تو قسمت چهارم توضیح میدیم چطور کوبرنتیز برای این پروژه کانفیگ کنیم و روی گوگل کلاد اجراش کنیم.</description>
                <category>عرفان</category>
                <author>عرفان</author>
                <pubDate>Tue, 16 Oct 2018 14:10:09 +0330</pubDate>
            </item>
                    <item>
                <title>ساخت پروژه با معماری مایکروسرویس، زبان گولنگ، اندپوینت رست، کوبرنتیز و... (قسمت دوم)</title>
                <link>https://virgool.io/golangpub/%D8%B3%D8%A7%D8%AE%D8%AA-%D9%BE%D8%B1%D9%88%DA%98%D9%87-%D8%A8%D8%A7-%D9%85%D8%B9%D9%85%D8%A7%D8%B1%DB%8C-%D9%85%D8%A7%DB%8C%DA%A9%D8%B1%D9%88%D8%B3%D8%B1%D9%88%DB%8C%D8%B3-%D8%B2%D8%A8%D8%A7%D9%86-%DA%AF%D9%88%D9%84%D9%86%DA%AF-%D8%A7%D9%86%D8%AF%D9%BE%D9%88%DB%8C%D9%86%D8%AA-%D8%B1%D8%B3%D8%AA-%DA%A9%D9%88%D8%A8%D8%B1%D9%86%D8%AA%DB%8C%D8%B2-%D9%88-%D9%82%D8%B3%D9%85%D8%AA-%D8%AF%D9%88%D9%85-pifwftn5g3xn</link>
                <description>این مطلب ادامه قسمت یک است. تو قسمت قبلی سرویس دهنده و سرویس گیرنده gRPC توسعه دادیم.تو این قسمت توضیح میدیم چطور اندپوینت‌های رست به سرویس gRPC اضافه کنیم. شما می‌تونید سورس کامل قسمت دوم رو از اینجا ببینید.برای اضافه کردن اندپوینت‌های رست میخوایم از لایبرری grpc-gateway استفاده کنیم. اینجا یه مطلب خوب هست که با جزئیات توضیح میده grpc-gateway چطور کار میکنه. https://medium.com/@thatcher/why-choose-between-grpc-and-rest-bc0d351f2f84 قدم اول: ملزومات رست رو به پروژه اضافه کنید.اول از همه باید grpc-gateway و پلاگین داکیومنت جنریتور swagger نصب کنیم.go get -u github.com/grpc-ecosystem/grpc-gateway/protoc-gen-grpc-gateway 
go get -u github.com/grpc-ecosystem/grpc-gateway/protoc-gen-swaggerلایبرری grpc-gateway در پوشه “%GOPATH%/src/github.com/grpc-ecosystem/grpc-gateway” نصب شد.به پوشه include از لایبرری grpc-gateway نیاز داریم.محتوای پوشه “%GOPATH%/src/github.com/grpc-ecosystem/grpc-gateway/third_party/googleapis/google” کپی کنید به پوشه “third_party/google” در پروژه‌تون.پوشه “protoc-gen-swagger/options” در پوشه third_party پروژه تون بسازید.mkdir -p third_party\protoc-gen-swagger\optionsحالا فایل annotations.proto و openapiv2.proto از پوشه “%GOPATH%/src/github.com/grpc-ecosystem/grpc-gateway/protoc-gen-swagger/options” به پوشه “third_party\protoc-gen-swagger/options” پروژه‌تون کپی کنید.فایل‌های پروژه‌تون باید شبیه به این شده باشه:قبل اینکه ادامه بدیم فرض میکنیم کد جنریتور proto روی سیستم شما نصبه، برای اطمینان کد پایین رو اجرا کنید:go get -u github.com/golang/protobuf/protoc-gen-goحالا باید ملزومات رست رو به فایل api/proto/v1/todo-service.proto اضافه کنیم (جزئیات از اینجا ببینید)  https://gist.github.com/amsokol/57cd11e08b07542837fdbaa596864b5c میتونید از اینجا درمورد Swagger در فایل‌های proto بیشتر بخونید.حالا فولدر “api/swagger/v1” ایجاد کنید (برای فایل‌های ساخته شده توسط Swagger) در روت اصلی پروژه:mkdir -p api\swagger\v1و محتوای فایل third_party/protoc-gen.cmd با کدهای پایین عوض کنید: https://gist.github.com/amsokol/b81df9c0d48a0ab50dcbb4cf62d60b52 مطمئن بشید تو پوشه go-grpc-http-rest-microservice-tutorial هستید و کد زیر را اجرا کنید:.\third_party\protoc-gen.cmdفایل “pkg/api/v1/todo-service.pb.go” آپدیت شد و دو فایل جدید هم ایجاد شد:pkg\api\v1\todo-service.pb.gw.go — فایل‌های مربوط به رستapi\swagger\v1\todo-service.swagger.json —  فایل‌های مربوط به داکیومنت جنریتور سوئگرساختار پروژه باید این شکلی شده باشه:و تمام، همه ملزومات رست رو به پروژه اضافه کردیم.قدم دوم: ایجاد سرور HTTP برای رستفایل  server.go در پوشه “pkg/protocol/rest” همراه با محتوای زیر ایجاد کنید: https://gist.github.com/amsokol/00c3c83000a1e741f6abd130843fba0e در یک پروژه واقعی نیاز دارید تا HTTPS کانفیگ کنی، می‌تونید یک نمونه اینجا ببینید.حالا محتوای فایل  “pkg/cmd/server.go” آپدیت کنید تا سرور HTTP اجرا کنه: https://gist.github.com/amsokol/23f4cb0ae6a440ffbe615f50cf92befa شما باید بدونید HTTP gateway روی gRPC پیاده شده، تست‌های من نشون میده ۱-۳ میلی ثانیه سرعت کمتر شده.ساختار پروژه باید شبیه به این شده باشه: و تمام.قدم سوم: اضافه کردن سرویس گیرنده رست.فایل “cmd/client-rest/main.go” همراه با محتوای زیر ایجاد کنید: https://gist.github.com/amsokol/f22d11a666d432f7dc68aea0cee34696 این تمام کدی بود که باید برای سرویس گیرنده می‌نوشتید، ساختار پروژه باید این شکلی شده باشه:آخرین قدم اینه مطمئن بشید سرور رست درست کار میکنه.توی ترمینال سرور gRPC همراه با رست رو بیلد بگیرید (پارامترها رو با کانفیگ دیتابیس‌تون عوض کنید):cd cmd/server 
go build . 
server.exe -grpc-port=9090 -http-port=8080 -db-host=&lt;HOST&gt;:3306 -db-user=&lt;USER&gt; -db-password=&lt;PASSWORD&gt; -db-schema=&lt;SCHEMA&gt;اگر خروجی ترمینال همچین چیزی بود:2018/10/14 13:53:27 starting HTTP/REST gateway... 
2018/10/14 13:53:27 starting gRPC server...به این معنیه که سرور اجرا شده، یه ترمینال دیگه باز کنید و سرویس گیرنده رست اونجا بیلد کنید و اجرا بگیرید:cd cmd/client-rest 
go build . 
client-rest.exe -server=http://localhost:8080اگر خروجی ترمینال همچین چیزی بود:2018/10/14 13:57:26 Create response: Code=200, Body={&quot;api&quot;:&quot;v1&quot;,&quot;id&quot;:&quot;3&quot;}
2018/10/14 13:57:26 Read response: Code=200, Body={&quot;api&quot;:&quot;v1&quot;,&quot;toDo&quot;:{&quot;id&quot;:&quot;3&quot;,&quot;title&quot;:&quot;title (2018-10-14T10:27:26.633575Z)&quot;,&quot;description&quot;:&quot;description (2018-10-14T10:27:26.633575Z)&quot;,&quot;reminder&quot;:&quot;2018-10-14T10:27:26Z&quot;}}
2018/10/14 13:57:26 Update response: Code=200, Body={&quot;api&quot;:&quot;v1&quot;,&quot;updated&quot;:&quot;1&quot;}
2018/10/14 13:57:26 ReadAll response: Code=200, Body={&quot;api&quot;:&quot;v1&quot;,&quot;toDos&quot;:[{&quot;id&quot;:&quot;3&quot;,&quot;title&quot;:&quot;title (2018-10-14T10:27:26.633575Z) + updated&quot;,&quot;description&quot;:&quot;description (2018-10-14T10:27:26.633575Z) + updated&quot;,&quot;reminder&quot;:&quot;2018-10-14T10:27:26Z&quot;}]}
2018/10/14 13:57:26 Delete response: Code=200, Body={&quot;api&quot;:&quot;v1&quot;,&quot;deleted&quot;:&quot;1&quot;}یعنی همه چی درست کار میکنه.خلاصه قسمت دوم:تمام این‌ها برای قسمت دوم بود. ما سرویس گیرنده و سرویس دهنده رست رو برای gRPC توسعه دادیم.سورس کد قسمت دوم از اینجا در دسترس شماست.قسمت سوم درباره پیاده سازی میدلورهای (logging/tracing) روی سرویس gRPC و همچنین رست است.</description>
                <category>عرفان</category>
                <author>عرفان</author>
                <pubDate>Sun, 14 Oct 2018 14:05:40 +0330</pubDate>
            </item>
                    <item>
                <title>ساخت پروژه با معماری مایکروسرویس، زبان گولنگ، اندپوینت رست، کوبرنتیز و... (قسمت اول)</title>
                <link>https://virgool.io/golangpub/%D8%B3%D8%A7%D8%AE%D8%AA-%D9%BE%D8%B1%D9%88%DA%98%D9%87-%D8%A8%D8%A7-%D9%85%D8%B9%D9%85%D8%A7%D8%B1%DB%8C-%D9%85%D8%A7%DB%8C%DA%A9%D8%B1%D9%88%D8%B3%D8%B1%D9%88%DB%8C%D8%B3-%D8%B2%D8%A8%D8%A7%D9%86-%DA%AF%D9%88%D9%84%D9%86%DA%AF-%D8%A7%D9%86%D8%AF%D9%BE%D9%88%DB%8C%D9%86%D8%AA-%D8%B1%D8%B3%D8%AA-%DA%A9%D9%88%D8%A8%D8%B1%D9%86%D8%AA%DB%8C-%D9%88-nyamxgxsuvqu</link>
                <description>داستان از اونجایی شروع شد که دنبال شغل تو کشورای دیگه گشتم، به عنوان گولنگ دولوپر اقدام کردم، تقریبا هر شرکتی که به گولنگ کار میکرد مایکروسرویس هم کار میکرد پس یه گوگل کردم و به این مطلب توی مدیوم رسیدم، دیدم جالبه گفتم ترجمه کنم تا بهتر تو ذهنم بمونه، این آموزش قرار نیست توضیح بده مایکروسرویس چیه یا چطور کار میکنه و کجا به درد شما میخوره، این مطلب فقط توضیح میده چطور پیاده سازی کنیم. خیلی خب توضیح بسته بریم سراغ متن اصلی نویسنده.تو اینترنت کلی مطلب درباره ساخت سرویس رست‌فول با معماری مایکروسرویس توسط زبان گولنگ وجود داره، دنبال بهترین شیوه برای شرکتم بودم و بیشتر این مطالب رو خوندم، بالاخره بهترین شیوه برای پیاده سازی معماری مایکروسرویس پیدا کردم، این شیوه توسط فریمورک protobuf/gRPC که گوگل توسعه میده پیاده سازی میشه، مطمئنم همه درباره‌ش خوندید یا بعضی‌ها همین الآن دارید از این شیوه استفاده می‌کنید، ولی فکر میکنم کمتر کسی تجربه توسعه رست‌فول با معماری مایکروسرویس توسط فریمورک protobuf/gRPC داشته باشه، من فقط یه مطلب تو مدیوم در این باره پیدا کردم: https://medium.com/@thatcher/why-choose-between-grpc-and-rest-bc0d351f2f84 من نمیخوام دوباره این مطلب رو توضیح بدم، میخوام قدم به قدم توضیح بدم چطور یک اپلیکیشن &quot;To Do list&quot; با معماری مایکروسرویس، gRPC و اندپوینت‌های رست توسعه بدید. می‌خوام نشون بدم چطور تو معماری مایکروسرویس تست و میدلور (request-ID and logging/tracing) بنویسید. همچنین با مثال توضیح میدم چطور این اپلیکیشن را بیلد بگیرید و در انتها روی کوبرنتیز دپلوی کنید.این آموزش ۴ قسمت خواهد داشت:قسمت یک: چطور یک سرویس‌دهنده و سرویس‌گیرنده gRPC بنویسیم.قسمت دو: چطور اندپوینت‌های رست رو به سرویس gRPC اضافه کنیم.قسمت سه: چطور میدلور (logging/tracing) به سرویس gRPC و رست اضافه کنیم.قسمت چهار: چطور کوبرنتیز رو کانفیگ کنید و health check اضافه کنیم. (احتمالا این قسمت رو ترجمه نمیکنم میتونید از وبلاگ اصلی بخونید)پیش‌نیازها:این آموزش زبان گولنگ نیست، شما باید از قبل کد زدن با گولنگ رو بلد باشید.باید گولنگ ورژن ۱.۱۱ یا بالاتر رو نصب کنید، ما می‌خوایم از قابلیت ماژول گولنگ استفاده کنیم که از این ورژن در دسترسه.شما باید نصب و راه‌اندازی MySQL بلد باشید، ما برای ذخیره اطلاعات از این دیتابیس استفاده می‌کنیم.ا- API firstاین یعنی چی؟ (این قسمت سادس و ترجمه‌ش واقعا بی‌معنی میشد)API definition MUST be language-, protocol-, transport- neutralAPI definition and API implementation MUST be loosely coupledAPI versioningI need to exclude manual work to sync API definition, API implementation and API documentation. I need API implementation stubs/skeleton and API documentation are generated from API definition automatically.در طول آموزش این نکات رو بیشتر باز میکنم.اپلیکیشن ToDo list با معماری مایکروسرویساین اپلیکیشن باید تسک‌هارو مدیریت کنه، پس جدولمون باید این فیلدهارو داشته باشه:ID (unique integer identifier)Title (text)Description (text)Reminder (timestamp)اپلیکیشن todo به طور معمول شامل اندپوینت‌های ایجاد، دریافت، اصلاح، حذف و همچین دریافت همه تسک‌ها می‌شود.قسمت اول: ایجاد سرویس gRPC CRUDقدم اول: تعریف API توسط protoقسمت یک درباره چگونگی ساخت توسعه سرویس‌دهنده gRPC CRUD و سرویس‌‌گیرنده‌ش برای تست است.سورس کد قسمت یک از اینجا در دسترس‌تونه https://github.com/amsokol/go-grpc-http-rest-microservice-tutorial/tree/part1 قبل اینکه شروع کنیم نیاز داریم تا ساختار پروژه رو تعریف کنیم.این یه الگوی خوب برای پروژه‌های گولنگه: https://github.com/golang-standards/project-layout لطفا این رو همونطوری که من استفاده کردم استفاده کنید.من از ویندوز ۱۰ ۶۴ بیت استفاده کردم، با این اوصاف فکر نمیکنم مشکلی برای تبدیل این دستورات برای مک یا لینوکس داشته باشید.در مرحله اول یک پوشه درجایی از کامپیوتر (برای اینکه بتونیم از گو ماژول استفاده کنیم باید خارج از GOPATH پوشه رو بسازیم) برای ایجاد پروژه بسازید. حالا پروژه رو مقدار دهی اولیه میکنیم:mkdir go-grpc-http-rest-microservice-tutorial

cd go-grpc-http-rest-microservice-tutorial

go mod init github.com/&lt;you&gt;/go-grpc-http-rest-microservice-tutorialحالا پوشه‌ای برای تعریف API ها میسازیم:mkdir -p api\proto\v1پوشه v1 برای ورژن یک API ساخته شده.درباره API ورژنینگ: این شیوه من برای ساخت ورژن‌های مختلف از API است، هر ورژن داخل فولدری با اسم خودش ساخته میشه.مرحله بعد ساخت فایل todo-service.proto داخل پوشه api\proto\v1 و اضافه کردن متد ایجادِ ToDoService برای شروع. https://gist.github.com/amsokol/bce8ddf48b834bd1d4689dc8b9ceb45c  شما میتونید از لینک زیر بیشتر درمورد proto بخونید: https://developers.google.com/protocol-buffers/docs/proto3 حالا Proto compiler binaries از اینجا دانلود کنید: https://github.com/protocolbuffers/protobuf/releases محتوای پوشه bin رو خارج کنید و آدرسش رو به path های environment variable سیستمون اضافه کنید.پوشه “third_party” داخل روت اصلی پروژه بسازیدمحتوای پوشه “include” داخل پوشه “third_party” کپی کنید.ساختار پروژه باید شبیه این باشدحالا کد جنریتور Proto نصب کنیدgo get -u github.com/golang/protobuf/protoc-gen-goفایل protoc-gen.cmd داخل پوشه “third_party” بسازید. برای مک و لینوکس فایل protoc-gen.sh بسازید و محتوای زیر رو در آن کپی کنید:protoc --proto_path=api/proto/v1 --proto_path=third_party --go_out=plugins=grpc:pkg/api/v1 todo-service.protoفولدری برای خروجی کدهای ساخته شده توسط کد ایجاد میکنیم:mkdir -p pkg/api/v1مطمئن بشید تو پوشه go-grpc-http-rest-microservice-tutorial هستید و دستور زیر را اجرا کنید:// for windows
.\third_party\protoc-gen.cmd

// for mac/linux
./third_party/protoc-gen.shاین دستور فایل todo-service.pb.go داخل فولدر “pkg/model/v1” میسازه.در نتیجه خروجی باید چیزی شبیه این باشهنتیجه جالب بود. حالا بیاید بقیه متدهای todo service اضافه کنیم و کامپایلش کنیم https://gist.github.com/amsokol/9a4cdd4d53f7f32757cc06d34d2c6097 و دوباره کامپایلر پروتو اجرا کنیم تا کدها آپدیت بشه:// for windows 
.\third_party\protoc-gen.cmd 

// for mac/linux 
./third_party/protoc-gen.shاین فایل ها باید داخل CI/CD ساخته بشه و به صورت دستی اجراش نکنید.تموم شد! API ها رو تعریف کردیم.قدم دوم: توسعه API توسط گولنگمن از MySQL استفاده میکنم، شما میتونید هر دیتابیسی که باهاش راحت ترید انتخاب کنید.با استفاده از کوئری پایین جدول ToDo می‌سازیم: https://gist.github.com/amsokol/245f734d10a410bf2701e4c9482c9ceb تو این پست توضیح ندادم چطور MySQL نصب کنید یا چطور دیتابیس توش ایجاد کنید و کوئری بزنید، انتظار دارم اینارو بلد باشید یا تو اینترنت سرچ کنید.فایل “pkg/service/v1/todo-service.go” با محتوای زیر ایجاد می‌کنید: https://gist.github.com/amsokol/bb7987f6247bdca80eec65ecf21a9b31 ساختار پروژه باید شبیه به این شده باشهو تمام.قدم سوم: نوشتن تست برای API مهم نیست داریم چی رو توسعه میدیم، باید تست بنویسیم، این یه قانونه.این یه لایبرری خوب برای ماک کردن دیتابیس‌های SQL هستش. https://github.com/DATA-DOG/go-sqlmock از این برای نوشتن تست سرویس تودولیست استفاده میکنم.این فایل رو در فولدر “pkg/service/v1” ذخیره کنید، پروژه تون باید شبیه عکس پایین شده باشه:تمام.قدم چهارم: ایجاد سرور gRPCفایل “pkg/protocol/grpc/server.go” همراه با محتوایات پایین بسازید: https://gist.github.com/amsokol/e52d2c3e601b128e9d02a2a5f1ad1fd5 فانکشن RunServer یک سرویس ToDo میسازه و سرور gRPC اجرا میکنه.در واقعیت شما باید TLS برای gRPC کانفیگ کنید، اینجا میتونید با یک مثال ببینید چطور باید این کارو انجام بدید.حالا فایل “pkg/cmd/server.go” همراه با محتوای پایین ایجاد کنید: https://gist.github.com/amsokol/46d5f4794c8d3b8122d93352ac074969 این فانکشن RunServer پارامترهای کامندلاین موقع اجرای برنامه دریافت میکنه، با سرور MySQL ارتباط برقرار میکنه، یک سرویس ToDo ایجاد میکنه و فانکشن RunServer قبلی رو از سرور gRPC اجرا میکنه.در آخر فایل “cmd/server/main.go” با محتوای زیر بسازید: https://gist.github.com/amsokol/7f27b64aeaa4a81541eb9c00f77f3f90 این تمام چیزی بود که سمت سرور اتفاق میفته، در آخر پروژه باید به این شکل باشه:قدم پنجم: ساخت کلاینت gRPCفایل “cmd/client-grpc/main.go” همراه با محتوای زیر بسازید: https://gist.github.com/amsokol/fa4c6046b8bc7d3dcc19329355e60173 این تمام اتفاقیه که سمت کلاینت میفته، خروجی پروژه‌تون باید شبیه عکس پایین باشه.قدم ششم: اجرای سرور و کلاینت gRPCاین قدم آخره تا اطمینان پیدا کنیم سرور gRPC درست کار میکنه.با استفاده از ترمینال سرور gRPC بیلد میگیریم و اجرا می‌کنیم (پارامترهای دیتابیس رو باتوجه به کانفیگ خودتون تغییر بدید)cd cmd/server
go build .
server.exe -grpc-port=9090 -db-host=&lt;HOST&gt;:3306 -db-user=&lt;USER&gt; -db-password=&lt;PASSWORD&gt; -db-schema=&lt;SCHEMA&gt;اگر همچین خروجی مشاهده کردید:2018/10/13 12:17:23 starting gRPC server...این یعنی سرور اجرا شده.حالا یه ترمینال دیگه باز میکنیم و کلاینت رو بیلد میگیریم و اجرا میکنیمcd cmd/client-grpc
go build .
client-grpc.exe -server=localhost:9090اگر خروجی شبیه این بود:2018/10/13 12:22:18 Create result: &lt;api:&quot;v1&quot; id:2 &gt;
2018/10/13 12:22:18 Read result: &lt;api:&quot;v1&quot; toDo:&lt;id:2 title:&quot;title (2018-10-13T08:52:18.373811Z)&quot; description:&quot;description (2018-10-13T08:52:18.373811Z)&quot; reminder:&lt;seconds:1539420738 &gt; &gt; &gt;
2018/10/13 12:22:18 Update result: &lt;api:&quot;v1&quot; updated:1 &gt;
2018/10/13 12:22:18 ReadAll result: &lt;api:&quot;v1&quot; toDos:&lt;id:2 title:&quot;title (2018-10-13T08:52:18.373811Z)&quot; description:&quot;description (2018-10-13T08:52:18.373811Z) + updated&quot; reminder:&lt;seconds:1539420738 &gt; &gt; &gt;
2018/10/13 12:22:18 Delete result: &lt;api:&quot;v1&quot; deleted:1 &gt;یعنی همه چی به خوبی کار میکنه.خلاصه قسمت اولتمام این قسمت اول بود، ما سرویس دهنده و سرویس گیرنده gRPC توسعه دادیم.سورس کد قسمت اول از اینجا در دسترس‌تونه.قسمت دوم اختصاص پیدا میکنه به چگونگی اضافه کردن اندپوینت‌های رست به سرویس gRPC که امروز توسعه دادیم.</description>
                <category>عرفان</category>
                <author>عرفان</author>
                <pubDate>Sat, 13 Oct 2018 12:31:45 +0330</pubDate>
            </item>
                    <item>
                <title>حساب‌فان چیه و چیکار میکنه؟</title>
                <link>https://virgool.io/hesabfun/%D8%AD%D8%B3%D8%A7%D8%A8%D9%81%D8%A7%D9%86-%DA%86%DB%8C%D9%87-%D9%88-%DA%86%DB%8C%DA%A9%D8%A7%D8%B1-%D9%85%DB%8C%DA%A9%D9%86%D9%87-hwvgfm64iepy</link>
                <description>احتمالا شماهم به عنوان یک برنامه نویس چیز زیادی از حسابداری، مالیات، ارزش افزوده و... نمیدونید ولی از اونجایی که ما از شرکت‌هایی که براشون کار میکنم حقوق می‌خوایم، شرکت‌ها باید درآمد داشته باشند و سیستم مالی پلتفرم‌هارو ما برنامه نویس‌ها مینویسیم، اینجای کار همیشه مشکلات زیادی داریم، مثلا وقتی که مدیریتون از در میاد داخل و میگه حسابداری فلان نوع گزارش فروش نیاز داره و شما سیستم جوری طراحی نکرده بودید تا بشه به راحتی همچین گزارشی گرفت، یا بدتر از اون وقتیه که باید گزارش‌هارو در قالبی مثل اکسل! خروجی بگیرید تا سیستم های حسابداری سنتی بتونند ماهی یکبار ایمپورتش کنند.یا نوع دیگه‌ای از مشکل وجود داره، وقتی که بخواید برای یک شرکت که تمام کارهاش سنتی انجام میده چیزی شبیه CRM یا فروشگاه اینترنتی راه‌اندازی کنید، گزارش موجودی کالاها یا آخرین لیست مشتری‌ها چیزی شبیه کابوسه، این سیستم‌ها هیچ API ندارند، این سیستم‌ها طراحی شدند تا چندتا حسابدار پشتش بشینند و گزارش‌های معمول بگیرند نه بیشتر.حساب‌فان چیه و چیکار میکنه؟حساب‌فان یک سیستم حسابداری API بیسه که به صورت اپن‌سورس و رایگان منتشر شده، شما میتونید به صورت رایگان از سایت حساب‌فان استفاده کنید یا در کمتر از ۵ دقیقه به وسیله داکر روی سرور شرکتتون اجراش کنید.حساب‌فان شمارو از نوشتن سیستم حسابداری برای پلتفرمی که مشغول توسعه‌اش هستید بی‌نیاز میکنه، کارهای مالیاتی و بیمه کارمندهارو به صورت اتوماتیک انجام میده، بخاطر ماهیت کلادش حسابدارها می‌تونند دورکاری کنند، به وسیله APIهاش می‌تونید پلاگین موردنیاز خودتون براش توسعه بدید، محدود به زبان خاصی نیست و با هر زبانی میشه براش پلاگین نوشت یا تو هر پلتفرمی ازش استفاده کرد، هسته حساب‌فان با GoLang نوشته شده که توسعه‌شو خیلی ساده کرده و از پرفورمنس خوبی برخورداره، همچنین از طریق وبسایت، اپ اندروید و آيفون و به زودی دسکتاپ قابل دسترسیه.الآن کجا هستیم؟فعلا درحال توسعه نسخه آلفا هستم، درحال حاضر استیبل نیست بهتره توی پروژه‌هاتون استفاده نکنید تا نسخه ۱ ریلیز کنم، درحال حاضر اگر نیاز به این پروژه دارید میتونید یکی از ریپازیتوری‌های core یا ui فورک کنید و توسعه بدید، به کمک شما نیاز داریم.کسب درآمد از حساب فان!نترسید، حساب‌فان رایگانه و قراره رایگانم بمونه ولی همیشه میتونید برای حساب‌فان پلاگین بنویسید و با هر بیزینس پلنی که درنظر دارید منتشرش کنید، پلاگین هایی مثل اپ سفارش گیری برای فروش مویرگی یا سفارش گیری برای کافه و رستوران، فروشگاه ساز اینترنتی، مدیریت فروشگاه‌های زنجیره‌ای، پلاگین‌های انبارداری و... .همونطور که گفتیم حساب‌فان یک پلتفرم کاملا منعطفه، برخلاف اپلیکیشن‌های حسابداری سنتی دست شمارو کاملا باز میذاره تا هر ایده‌ای رو اجرایی کنید.البته بهتره تا منتشر شدن نسخه یک صبر کنید، فعلا تغییرات زیادی انجام میدم و اصلا استیبل نیست.من عرفانم توسعه دهنده حساب‌فان، میتونید با ایمیل آدرس erfun@hesabfun.com با من در ارتباط باشید.صفحه گیت‌هاب حساب‌فان: https://github.com/hesabFunداکیومنت حساب‌فان: https://docs.hesabfun.com</description>
                <category>عرفان</category>
                <author>عرفان</author>
                <pubDate>Sat, 08 Sep 2018 12:42:41 +0430</pubDate>
            </item>
                    <item>
                <title>حسابداری برای همه (دفتر معین)</title>
                <link>https://virgool.io/accountancy/%D8%AD%D8%B3%D8%A7%D8%A8%D8%AF%D8%A7%D8%B1%DB%8C-%D8%A8%D8%B1%D8%A7%DB%8C-%D9%87%D9%85%D9%87-%D9%82%D8%B3%D9%85%D8%AA-%D8%B4%D8%B4%D9%85-nf7kgn3wmzyz</link>
                <description>قسمت‌های پیشین:حسابداری برای همه (قسمت اول)حسابداری برای همه (تراز نامه)حسابداری برای همه (حساب دوطرفه، حساب تی)حسابداری برای همه (ثبت ساده و مرکب)حسابداری برای همه (دفتر روزنامه، دفتر کل)دفتر معین: دفتری است که برای تفکیک و مجزا ساختن هریک از حساب‌های دفترکل برحسب مقتضیات و شرایط حساب ممکن است نگهداری شود که جنبه کمکی، فرعی و تکمیلی دارد.برای مثال اگر یک موسسه دارای ۱۰ حساب جاری در بانک باشد ممکن است از دفتر معین بانک استفاده کند که در این صورت حساب بانک در دفترکل یک حساب کنترل خواهد بود. دفتر معین جز دفاتر قانونی نیست. دفاتر معینی که معمولا مورد استفاده قرار می‌گیرند عبارتند از: دفتر معین بانک، دفتر معین بدهکاران، دفتر معین بستانکاران، دفتر معین اموال و... به طور مثال موسساتی که نشریان متعددی دارند در دفترکل حسابی به نام منترل حساب بدهکاران افتتاح و جمع افزایش و کاهش در حساب‌های مشتریان در آن ثبت و برای هر مشتری حسابی جداگانه در دفتر معین بدهکاران نگهداری می‌نمایند که نشان می‌دهد هر مشتری چه مقدار خرید کرده، چه مبلغی پرداخت نموده و چقدر بدهکار است. بدهی است بعد از ثبت تمام معاملات یک دوره در دفترکل و دفتر معین بدهکاران مانده حساب منترل بدهکاران در دفتر کل جمع مبلغ بدهکاران را نشان می‌دهد که باید با حاصل جمع جساب‌های مشتریان در دفتر معین بدهکاران مساوی باشد.مثال: قسمتی از فعالیت‌های مالی موسسه خدماتی آرش در فروردین ماه به شرح زیر است:۱-۱ ارائه خدمات به فروشگاه احمدی به مبلغ ۳۰۰.۰۰۰ ریال به طور نسیه.۱-۲ ارسال صورت حساب انجام خدمات به موسسه بهروز ب مبلغ ۲۸۰.۰۰۰ ریال.۱-۳ دریافت قسمتی از بدهی از فروشگاه احمدی به مبلغ ۲۰۰.۰۰۰ ریال.۱-۴ ارائه صورت حساب انجام خدمات به موسسه جعفری به مبلغ ۱۵۰.۰۰۰ ریال.۱-۵ دیافت قسمتی از بدهی موسسه بهروز به مبلغ ۱۰۰.۰۰۰ ریالمطلوب است:ثبت فعالیت‌های مالی فوق در دفتر روزنامه عمومی.نقل اقلام ثبت شده به دفاتر معین و دفترکل.دفتر روزنامه عمومیدفتر معین بدهکاران - مجموع ۴۳۰.۰۰۰ ریالدفترکل</description>
                <category>عرفان</category>
                <author>عرفان</author>
                <pubDate>Wed, 25 Apr 2018 14:19:36 +0430</pubDate>
            </item>
                    <item>
                <title>رست VS گراف‌کیوال</title>
                <link>https://virgool.io/apieco/%D8%B1%D8%B3%D8%AA-vs-%DA%AF%D8%B1%D8%A7%D9%81%DA%A9%DB%8C%D9%88%D8%A7%D9%84-bpl24nw01ek7</link>
                <description>اگه دولوپر هستید حتما درمورد استاندارد رست شنیدید و کار کردید، چندسالی هست ابزار جدیدی برای API معرفی شده به اسم گراف‌کیوال (GraphQL). اگه این مدت مشغول توسعه سرویسی بوده باشید ممکنه از خودتون پرسیده باشید آیا باید رست کنار بذارم و از گراف‌کیوال استفاده کنم؟ بذارید قبل این تصمیم اول ببینیم رست و گراف‌کیوال چی هستند.رست (REST) چیست؟رست (به انگلیسی REST) مخفف REpresentational State Transfer یکی از محبوب‌ترین استانداردهای معماری API هست که روی فیلدینگ (Roy Fielding) در پایان نامه دکترای خودش معرفی کرد. این استاندارد تشویق میکنه لایه بک‌اند کاملا از فرانت‌اند جدا کنیم، این استاندارد یک سری قوانین وضع کرده، برای مثال:همه دیتاها باید با URL های یکتا دردسترس باشند.دیتاها نباید انکریپت بشن.از سشن استفاده نکنیم.فقط از پروتکل HTTP/HTTPS استفاده کنیم.برای عملیات CRUD از متدهای Get, Post, Put, Delete استفاده کنیم.بازگشت اندپوینت‌ها باید Json, XML, atom, OData یا... باشد.و کلی قواعد دیگه که می‌تونید از اینجا به صورت کامل بخونید.فرق REST و RESTful: درجریان باشید به این استاندارد میگن رست و در سرویس‌هایی که از این استاندارد استفاده کرده باشن میگن این سرویس رست‌فوله.نکته دوم: در این پست منظور از کلاینت فرانت‌اند دولوپره نه کاربر نهایی.گراف‌کیوال (GraphQL) چیست؟گراف‌کیوال یک query language برای API هست. تو سال ۲۰۱۲ توسط فیسبوک ایجاد شد و درسال ۲۰۱۵ به صورت اپن سورس منتشر شد. این نه یک استاندارده نه یک وب سرویس. گراف‌کیوال واسط بین کوئری و دیتاسورس‌های شماست. دیتاسورس شما میتونه دیتابیس یا وب سرویس باشه.رست سالها به عنوان استاندارد وب سرویس‌ها استفاده شد. از آنجایی که از پروتکل HTTP و متدهای استاندارد Get, Post, Put, Delete استفاده می‌کرد به شدت بین وب اپ ها محبوب شد. درنهایت بهتره اپلیکیشن‌ها و پلتفرم‌ها رو یه وب سرویس بنویسیم، چون به هر دیتایی نیاز پیدا کنیم با یک اندپوینت خاص بهش دسترسی داریم، ساخت اپ موبایل، وب، دسکتاپ و... به سادگی قابل اجراس، سرعت وب اپ بسیار زیاد میشه چون نیازی به رفرش صفحه نیست و با عوض شدن صفحه فقط اِلمان‌های قبلی حذف و جدیدها را اضافه می‌کنیم، همینطور لود سرور خیلی پایین میاد چون تو هر صفحه نیاز نیست کل دیتاها از اول بگیریم و فقط اِلمان‌های جدید صفحه رو از وب سرویس دریافت میکنیم، اینطوری هزینه سرور کاهش پیدا میکنه، پیچیدگی فنی توسعه کم میشه و...نقطه ضعف‌های RESTاگرچه سرویس‌های رست‌فول بسیار موفق بودن ولی با فراگیر شدنشون ضعف‌هایی رو از خودشون نشون دادن.۱- اندپوینت‌های مختلف (ریکوئست‌های مختلف)در سرویس‌های رست‌فول هر URL یک دیتای خاص رو نشون میده، پس برای صفحه‌ای که اطلاعات کاربر، پست‌های کاربر، کامنت‌ها و... نمایش میده باید چند URL مختلف صدا کنیم تا تمام اطلاعات صفحه نمایش بدیم.برای مثال یک وبلاگ درنظر بگیرید، شما یک سری مقاله دارید و تعدادی کامنت، پس در یک سرویس رست‌فول اندپوینت‌ها باید به این صورت باشن:GET /posts/&lt;postId&gt; - To fetch a particular post
GET /posts/&lt;postId&gt;/comments - To fetch all comments related to a post
GET /posts/&lt;postId&gt;/comments/&lt;commentId&gt; - To fetch a particular comment of a particular postهمینطور که می‌بینید هرچه تعداد اِلمان‌های صفحه بیشتر میشه تعداد اندپوینت‌ها افزایش پیدا میکنه، هرچه برنامه بزرگتر بشه توسعه و نگهداری این اندپوینت‌ها سخت تر میشه. (البته هنوزم از روش‌های سنتی  مثل MVC بهتره)۲- ارسال و دریافت دیتاوقتی شما یک اندپویت صدا می‌کنید احتمالا مقداری اطلاعات توش هست که به دردتون نمی‌خوره و هیچوقت ازشون استفاده نمی‌کنید، همینطور گاهی بخاطر یک دیتای کوچیک مجبورید یه اندپوینت حجیم صدا کنید، این یه مشکل رایج تو رست‌‌فوله. گاهی پیش میاد شما فقط به ۲-۳تا از دیتاها نیاز دارید ولی اندپوینت ۲۰-۳۰ تا دیتا برمی‌گردونه، اینطوری هم دیتای سنگین‌تری دریافت شده هم زمان پردازش بک‌اند بیشتر شده. گاهی هم به علت پراکندگی دیتا مجبورید چند ریکوئست ارسال کنید که اینم باعث افزایش زمان لود صفحه و ناراضی شدن کاربر میشه.۳- ورژن‌های مختلف APIوقتی شما دیتای یکی از اندپوینت‌ها رو عوض میکنید مجبورید برای اینکه کاربرایی که (فرانت‌اند دولوپرها) از API شما استفاده کردند دچار مشکل نشن ورژن جدیدی برای API بدید و قبلی به حال خودش رها کنید تا دولوپرها بیان رو نسخه جدید.این یعنی همیشه بعد آپدیت یک سری کاربر هنوز از نسخه قدیمی استفاده می‌کنند و اگر باگی پیدا بشه مجبورید همه ورژن‌ها رو آپدیت کنید، برای همه ورژن‌ها داکیومنت بنویسید، کدهای تکراری توی بک‌اند و...۴- کلاینت در تاریکیهتا زمانی که کلاینت (فرانت‌اند دولوپر) اندپوینت مربوطه صدا نکنه نمیدونه توی ریسپانس چه دیتایی هست، این یعنی همیشه جای اشتباه وجود داره و یک سری اندپوینت که هیچوقت استفاده نمیشن.نقاط قوت GraphQLگراف‌کیوال برای اولین بار توسط فیسبوک و برای رفع ضعف‌های رست ایجاد شد.۱- یک ریکوئست ارسال کن و تمام دیتاها دریافت کنیک سرویس گراف‌کیوال تنها یک اندپوینت داره و کاربر میتونه کوئری موردنظرش رو بهش ارسال کنه و دیتای مورد نیازشو بگیره، بذارید با همون مثال وبلاگ توضیح بدیم:{
    findPost(id: &lt;postId&gt;) {
        id
        title
        content
        author
        comments {
            id
            comment
            commentedBy
        }
    }
}همنیطور که میبینید تمام اطلاعات موردنیازمون تنها با یک ریکوئست دریافت کردیم، پس اگر به دیتای بیشتری نیاز داشته باشم فقط باید کوئری بزرگتری بنویسم.‌۲-ـ Strongly Typedگراف‌کیوال کاملا Strongly Typed است، این باعث میشه نیازی به داکیومنت نداشته باشید و کلاینت (فرانت‌اند دولوپر) بدونه بعد از ارسال کوئری چه نوع دیتایی دریافت می‌کنه.۳- کلاینت همه چیز مشخص می‌کنهگراف‌کیوال اجازه میده کلاینت مشخص کنه ریسپانس چه شکلی باشه و چه دیتایی توش وجود داشته باشه، این قابلیت احتمال دریافت اطلاعات بی‌استفاده از بین میبره.۴- تکامل API (ورژنینگ)از اونجایی که تمام ریسپانس‌ها طبق شِمایی (schema) که کلاینت ارسال کرده هست، اضافه کردن فیلد جدید به API مشکلی ایجاد نمی‌کنه، همچنین گراف‌کیوال برای حذف فیلدها قابلیتی داره به اسم &#x60;@deprecated&#x60; . بنابراین از ایجاد ورژن‌های مختلف بی‌نیاز می‌شیم.۵- پروتکل انتقال دیتاگراف‌کیوال میتونه روی هر پروتکلی مثل HTTP, HTTPS, WebSockets, TCP, UDP و... اجرا بشه.نقاط ضعف گراف‌کیوالخب، گراف‌کیوال عالیه، ولی اینم مثل بقیه چیزا نقاط ضعف خودش داره.۱- سیستمی برای کش کردن وجود ندارهگراف‌کیوال برخلاف رست‌فول که از سیستم کش HTTP استفاده میکنه سیستم کش برای موبایل و بروزر نداره، اگرچه ابزارهایی مثل Relay سیستم کش در اختیار ما قرار میده ولی مثل رست‌فول بالغ و جا افتاده نیست.۲- مانیتورینگ و گزارش خطاهااستاندارد رست برای گزارش خطاها از کدهای HTTP استفاده می‌کنه.این باعث میشه API برای برنامه نویسان بسیار ساده باشه، ولی گراف‌کیوال همه ریسپانس‌ها با استاتوس کد ۲۰۰ ارسال میکنه، یک ریسپانس خطا با سرویس گراف‌کیوال با استاتوس کد ۲۰۰ به طور معمول به این شکله:{
    errors: [
        { 
            message: &#039;Some error occurred&#039;
        }
    ]
}این باعث میشه مدیریت و مانیتورینگ خطاها خیلی سخت بشه.۳- نمایش ساختار و حمله به منابعبرخلاف سرویس‌های رست‌فول در سرویس‌های گراف‌کیوال برای نوشتن کوئری کاربر باید با دیتا استراکچر شما آشنا باشه، اگر شما API خودتون به شخص ثالثی بدید در واقع دیتا استراکچر خودتون بهش دادید، پس باید سرویس خیلی خوبی نوشته باشید که کلاینت نتونه join های سنگین بزنه تا سرویس شما زیر حمله Dos نره.۴- امنیت - احراز هویت و مجوزهادر جامعه گراف‌کیوال هنوز درمود نحوه ایجاد امنیت برای سرورهای گراف‌کیوال به نقطه نظر واحدی نرسیدن. هنوز استانداردی برای ادغام احراز هویت و مجوزها در گراف‌کیوال وجود نداره. معمولا فقط برای لایه بیزینسی احراز هویت انجام میدیم، اما با توجه به ضعف شماره ۳ باید به کسانی که لاگین نکردن اجازه کوئری زدن بدیم؟ این سوال هنوز درمورد GraphQL جواب داده نشده.۵- هزار و یک مشکل کوئریدر سرویس‌های رست‌فول خیلی سادس یه اندپوینت مورد بررسی قرار بدیم، از SQL لاگ بگیریم و سرعتش بالا ببریم، اما درمورد گراف‌کیوال که طبیعتش دینامیکه خیلی سخته کوئری‌ش به دست آورد و بهینه سازی کرد. گاهی ممکنه مجبور به حل هزار و یک مشکل معمول + مشکل Join های سنگین بخورید.۶- اکوسیستم جواناز لحاظ قدمت گراف‌کیوال بین بقیه استانداردها مثل بچه‌س، این یعنی تو کار ممکنه به هزار و یکجور مشکل جدید بخورید و کسی قبلا راه حل مشخصی پیدا نکرده و باید ساعتها وقت صرف پیدا کردن راه حل بکنید. پس برای استفاده از لایبرری‌ها خیلی دقت کنید.نتیجهبذارید اینجوری شروع کنم که گراف‌کیوال یه ابزاره و رست یک الگوی معماری. میشه گفت که گراف‌کیوال می‌تونه جایگزین رست بشه، اما در این عصر از میکروسرویس‌ها و ساخت API ها بسیار کوچک ما میتونیم هردوتاش داشته باشیم.سرورهای گراف‌کیوال پرفورمنس رو به عنوان اولیت اصلی حفظ می‌کنند درحالی که سرویس رست‌فول سرویس‌مون رو قابل اطمینان نگه میداره.گراف‌کیوال می‌تونه توسط سرویس رست‌فول در دسترس قرار بگیره، برای مثال روی اندپوینت &#x60;/graphql&#x60; قرار بگیره و کوئری‌ها اجرا کنه درحالی که رست فول برای موراد خاص مورد استفاده قرار بگیره.در مواردی بعضی پلتفرم‌ها ممکنه سرویس گراف‌کیوال عملکرد بهتری داشته باشه و در بعضی مواقع رست‌فول. پس قبل اینکه بگید کدوم عملکرد بهتری داره، خوبه نیازهاتون بررسی کنید و طبق اون نتیجه بگیرید از کدوم استفاده کنید.منبع</description>
                <category>عرفان</category>
                <author>عرفان</author>
                <pubDate>Mon, 16 Apr 2018 14:57:05 +0430</pubDate>
            </item>
                    <item>
                <title>حساب‌فان (CI برای انگولار و کوردوا)</title>
                <link>https://virgool.io/@erfun/%D8%AD%D8%B3%D8%A7%D8%A8%D9%81%D8%A7%D9%86-ci-%D8%A8%D8%B1%D8%A7%DB%8C-%D8%A7%D9%86%DA%AF%D9%88%D9%84%D8%A7%D8%B1-%D9%88-%DA%A9%D9%88%D8%B1%D8%AF%D9%88%D8%A7-thsfqyis2nqf</link>
                <description>احتمالا این پست آخر از این مجموعه آموزش باشه، تقریبا تمام نکات مهم توضیح دادم بقیه‌ش خورده کاریه، تو این پست توضیح میدم چطور بیلد اتوماتیک برای اندروید اضافه کنیم و فایل APK خودکار به کانال اسلک ارسال کنیم.خب بازم از سرویس circleci استفاده میکنیم. (قبلا توضیح دادم چرا) اول از همه فایل config.yml داخل فولدر .circleci ایجاد می‌کنیم و این محتویات براش قرار میدیم:version: 2
jobs:
  build:
    docker:
      - image: erfun/gitlab-ci-android-26:latest

    working_directory: ~/angular-cli-circleci
    steps:
      - checkout
      - restore_cache:
          key: dependency-cache-{{ checksum &quot;package-lock.json&quot; }}
      - run:
          name: install packages
          command: npm install
      - save_cache:
          key: dependency-cache-{{ checksum &quot;package-lock.json&quot; }}
          paths:
            - ./node_modules
            - ./cordova/node_modules
            - /root/.gradle
      - run:
          name: build web app
          command: ./node_modules/@angular/cli/bin/ng build --prod
      - run:
          name: build android
          command: |
              cd cordova
              mkdir www
              npm install -g cordova
              npm install
              cordova platform add android
              cordova build android
              curl -s -F channels=&quot;ci&quot; -F file=@&quot;/root/angular-cli-circleci/cordova/platforms/android/app/build/outputs/apk/debug/app-debug.apk&quot; -F initial_comment=&quot;Aew App ${CIRCLE_PROJECT_USERNAME} ${CIRCLE_PROJECT_REPONAME} ${CIRCLE_SHA1}&quot; -F token=${SLACK_TOKEN} https://slack.com/api/files.uploadخب در مرحله اول ایمیجی که خودم برای بیلد اندروید نیازه و روی داکرهاب پوش کردم معرفی کردم، داکر فایل این ایمیج روی گیت‌هاب گذاشتم می‌تونید از اینجا کدش نگاه کنید و دقیقا متوجه بشید با چی سر و کار دارید.بعد این مرحله کد مربوط به کش npm میبینید، و مرحله بعد نصب پکیج‌های انگولار، در مرحله بعد پیکج‌های نصب شده کش کردیم و بعدش از کل اپ بیلد گرفتیم.می‌تونستیم تست هم اینجا ران کنیم ولی چون هنوز تست ننوشتم بیخیال شدم.مرحله بعد رفتیم برای بیلد اپ اندروید با کوردوا. فولدر www ساختیم وگرنه کوردوا خطا میده، کوردوا نصب کردیم، پکیج‌هایی که تو کوردوا نصب کرده بودیم دانلود کردیم، پلتفرم اندروید اضافه کردیم و بیلد گرفتیم.تو خط آخر با کمک این بش اسکریپت فایل APK روی کانال اسلک قرار دادیم تا به راحتی نصب و تست کنیم، از این آدرس توکن برای این کار ساختیم و تو اینوایرمنت‌های CI تعریف کردیم.میشد روی کانال تلگرامم ارسال کرد ولی با ارسال فایل توسط ربات روی کانال یکم مشکل داشتم، اگه شما بش اسکریپت‌شو نوشتید لطفا برام بفرستید به پروژه اضافه کنم.نکته پایانی: میشد این چند مرحله تو چند استیج تعریف کرد ولی دیدم فعلا نیازی نیست و خواستم بیلد سریعتر اجرا بشه پس تو یه استیج نوشتم ولی بعدا تقسیم میشه بین ۳ تا استیج.</description>
                <category>عرفان</category>
                <author>عرفان</author>
                <pubDate>Thu, 12 Apr 2018 12:09:08 +0430</pubDate>
            </item>
                    <item>
                <title>حساب‌فان (انگولار دوست نداشتنی)</title>
                <link>https://virgool.io/JavaScript8/%D8%AD%D8%B3%D8%A7%D8%A8%D9%81%D8%A7%D9%86-%D8%A7%D9%86%DA%AF%D9%88%D9%84%D8%A7%D8%B1-%D8%AF%D9%88%D8%B3%D8%AA-%D9%86%D8%AF%D8%A7%D8%B4%D8%AA%D9%86%DB%8C-i9iqd15xt6hb</link>
                <description>تو پست‌های قبل بک‌اند نوشتیم، پروژه فرانت‌اند ایجاد کردیم، برای وب و اندروید بیلد گرفتیم، الآن میخوایم شروع کنیم با انگولار کد زدن امیدوارم مبانی Angular cli بلد باشید چون قرار نیست خیلی تو مسائل ریز بشم، اول اینکه با scss کار میکنم برام راحت‌تر از css هست، با دستور پایین پیش فرض پروژه scss میشه:ng set defaults.styleExt=scssبه چندتا ماژول اضافی نیاز داریم برای ساخت اپ مورد نظرمون، پس با این دستورات نصب‌شون می‌کنیم:npm install @angular/material @angular/cdk @angular/animations hammerjs @angular/flex-layout angular2-moment ng2-webstorage angular2-jwt @ngx-translate/core rxjs --saveحالا ماژول shared ایجاد می‌کنیم، ماژول‌هایی مثل ترجمه خودکار، لاگین کاربر، متریال دیزاین و... که برای تمام کامپونتت‌ها نیاز داریم اینجا اضافه می‌کنیم. (نمی‌خوام شلوغش کنم چون تعداد ماژول‌ها زیاد بود کد ایجا نمیذارم از گیت بخونید)برای سرویس ترجمه‌ای که استفاده می‌کنیم در فولدر src/assets/i18n بعد ایجاد هر کامپوننت فایل ترجمه بسازید.همچنین به روتر نیاز داریم، ماژول روتر خارج از شرد ایجاد می‌کنیم.فونت ایران یکان استفاده کردم، رایگان نیست ولی به نظرم بهترینه، برای این اپ لایسنس گرفتم اگر شمام فورک دیگه‌ای از پروژه راه انداختید حتما فونت بخرید، البته اگر اپ‌تون رایگان بود لازم نیست بخرید بگید خودم لایسنس می‌گیرم.ماژول لاگین نوشتیم، واضحه چیکار میکنه، به همین خاطر براش سرویسی راه انداختیم تا تو همه اپ اطلاعات کاربر داشته باشیم.منوی سمت راست اضافه کردیم که بعد لاگین نمایش داده میشه.همچنین یه اندپوینت و میدلور به بک‌اند اضافه کردیم.راستش فرانت کد زدن به اندازه کافی برام خسته کننده هست و توضیح دادنش مثل شکنجه کردنمه، کلیت اپ همینه، تست نداره فعلا ولی بهش اضافه میکنم. اگه جایی سوالی دارید بپرسید حتما جواب میدم.تو قسمت بعد بیلد اتوماتیک برای وب و اپ اندروید بهش اضافه میکنیم.از این به بعد استراکچر کد آمادس فقط باید یه وب سرور ران کنیم و تک تک فیچرها اضافه کنیم، خلاصه می‌تونیم شروع کنیم به کار اصلی‌مون :)برای تست چیزی که تا الآن نوشته شده می‌تونید بک‌اند بیلد بگیرید و اجرا کنید (داکیومنت داره کافیه نگاهی به ریپازیتوری بکنید) و بعد فرانت کلون کنید و طبق داکیومنت (اینم یه نگاهی به ریپو کنید کامل نوشتم) بیلد بگیرید، حالا می‌تونید نتیجه این دو هفته کد زدن ببینید.</description>
                <category>عرفان</category>
                <author>عرفان</author>
                <pubDate>Fri, 16 Mar 2018 13:33:13 +0330</pubDate>
            </item>
                    <item>
                <title>حساب‌فان (آیکون و اسپلش اسکرین)</title>
                <link>https://virgool.io/@erfun/%D8%AD%D8%B3%D8%A7%D8%A8%D9%81%D8%A7%D9%86-%D8%A2%DB%8C%DA%A9%D9%88%D9%86-%D9%88-%D8%A7%D8%B3%D9%BE%D9%84%D8%B4-%D8%A7%D8%B3%DA%A9%D8%B1%DB%8C%D9%86-hvc1liu6djse</link>
                <description>یه نگاهی به فایل cordova/config.xml می‌کنیم، اطلاعات دیسکریپشن و... مناسب با پروژه عوض می‌کنیم، و بعد میرسیم به قسمت پلتفرم‌ها، از اونجایی که تو قسمت قبل اندروید اضافه کرده بودیم اسم اندروید اینجا می‌بینیم، کد زیر بهش اضافه میکنیم:&lt;icon density=&quot;ldpi&quot; src=&quot;res/icon/android/icon-36-ldpi.png&quot; /&gt;
&lt;icon density=&quot;mdpi&quot; src=&quot;res/icon/android/icon-48-mdpi.png&quot; /&gt;
&lt;icon density=&quot;hdpi&quot; src=&quot;res/icon/android/icon-72-hdpi.png&quot; /&gt;
&lt;icon density=&quot;xhdpi&quot; src=&quot;res/icon/android/icon-96-xhdpi.png&quot; /&gt;با این کار به کردوا می‌گیم آیکون اپ برای رزولوشن‌های مختلف کدومه، نکته‌ای که باید درنظر بگیرید اینه ممکنه شما ببینید این فایل‌ها وجود داشتند پس این کد اضافه نکنید، بدونید همچنان آیکون پیش فرض خودش میذاره و اهمیتی نمیده فایل عوض شده چون موقع کامپایل فایل از این مسیر نمی‌خونه.نکته: فعلا یه لوگوی زشت به اپ اضافه کردیم ولی بعدا لوگو عوض میکنیم، این فقط برای تسته.برای ساخت لوگو کلی سایت logo generator وجود داره که لوگوهای زشتی بهتون تحویل میده، میتونید مثل من از این سایتا استفاده کنید یا با یه طراح حرفه‌ای کار کنید.بعد آیکون میرسیم سراغ اسپلش اسکرین، اسپلش اسکرین صفحه‌ای هست که بعد بازکردن اپ به مدت یک تا چندثانیه نمایش داده میشه و معمولا لوگوی سرویس مربوطه وسطشه.اسپلش اسکریناسپلش اسکرین مثل آیکونه، اگه فقط فایل عوض کنید تغییری نمی‌بینید پس کد زیر به فایل کانفیگ اضافه می‌کنیم:&lt;splash density=&quot;land-hdpi&quot; src=&quot;res/screen/android/screen-hdpi-landscape.png&quot; /&gt;
&lt;splash density=&quot;land-ldpi&quot; src=&quot;res/screen/android/screen-ldpi-landscape.png&quot; /&gt;
&lt;splash density=&quot;land-mdpi&quot; src=&quot;res/screen/android/screen-mdpi-landscape.png&quot; /&gt;
&lt;splash density=&quot;land-xhdpi&quot; src=&quot;res/screen/android/screen-xhdpi-landscape.png&quot; /&gt;
&lt;splash density=&quot;port-hdpi&quot; src=&quot;res/screen/android/screen-hdpi-portrait.png&quot; /&gt;
&lt;splash density=&quot;port-ldpi&quot; src=&quot;res/screen/android/screen-ldpi-portrait.png&quot; /&gt;
&lt;splash density=&quot;port-mdpi&quot; src=&quot;res/screen/android/screen-mdpi-portrait.png&quot; /&gt;
&lt;splash density=&quot;port-xhdpi&quot; src=&quot;res/screen/android/screen-xhdpi-portrait.png&quot; /&gt; و تمام، دراصل اینا یه پلاگین برای کوردوا هستند، اسپلش اسکرین تنظیمات بیشتری داره که میتونید به کمکش تنظیمات حرفه‌ای تری اعمال کنید. در مرحله آخر نمی‌خوایم هردفعه قبل بیلد اپ موبایل اول وب بیلد بگیریم پس این هوک به کانفیگ اضافه میکنیم:&lt;hook src=&quot;hooks/prepareAngular2App.js&quot; type=&quot;before_prepare&quot; /&gt;و توش گفتیم قبل بیلد چه هوکی اجرا کنه، پس هوک بیلد اپ انگولار مینویسیم:const execSync = require(&#039;child_process&#039;).execSync;

module.exports = function (context) {
  console.log(&#039;Building Angular 2 application into &quot;./www&quot; directory.&#039;);
  const basePath = context.opts.projectRoot;

  console.log(execSync(
    &quot;./node_modules/@angular/cli/bin/ng build --target=production --environment=prod --output-path cordova/www/ --base-href ./ --aot&quot;,
    {
      maxBuffer: 1024 * 1024,
      cwd: basePath + &#039;/..&#039;
    }).toString(&#039;utf8&#039;)
  );
};حالا که تنظمات اولیه انجام دادیم آماده‌ایم کد زدن با انگولار شروع کنیم، پست بعدی درمورد انگولار می‌نویسم.</description>
                <category>عرفان</category>
                <author>عرفان</author>
                <pubDate>Tue, 13 Mar 2018 15:38:35 +0330</pubDate>
            </item>
                    <item>
                <title>حساب‌فان (رابط کاربری)</title>
                <link>https://virgool.io/@erfun/%D8%AD%D8%B3%D8%A7%D8%A8%D9%81%D8%A7-%D8%B1%D8%A7%D8%A8%D8%B7-%DA%A9%D8%A7%D8%B1%D8%A8%D8%B1%DB%8C-e41kxmxswrb9</link>
                <description>خب بک‌اند به جایی رسید که بشه یه رابط کاربری ساده روش پیاده کرد، پس میریم سراغ انگولار و فعلا برای وب و اندروید بیلد میگیریم و بعدا آیفون، دسکتاپ و... اضافه می‌کنیم، قسمت خوب ماجرا اینه قراره بیلد اتوماتیک باشه.اول باید یه پروژه انگولار ایجاد کنید، من از انگولار سی‌ال‌آی استفاده می‌کنم، اگه تجربه کار باهاش ندارید کافیه نگاهی به سایتش بکنید و همون چند دستور اول وارد کنید تا پروژه ایجاد بشه.صفحه اول انگولار سی‌ال‌آیخیلی خب، پروژه ایجاد کردیم و گذاشتیم رو گیت‌هاب، کافیه ریپو کلون کنید و دستور npm install بزنید تا پکیج‌ها نصب بشن، حالا با دستور ng serve اپ اجرا میشه و با آدرس localhost:4200 صفحه پیش فرض انگولار می‌بینید. صفحه پیش فرض انگولار ۵می‌خوایم همین صفحه ساده تبدیل کنیم به اپ موبایل برای انواع پلتفرم‌ها، آپاچی کوردوا این کار برامون انجام میده. (اپ قرار نیست همین شکلی بمونه بعدا شبیه تمام اپ‌های استاندارد پلی استور میشه)در نهایت اپ این شکلی میشهاز دستور cordova create cordova com.hesabfun.ui hesabFun برای ایجاد فایل‌های اولیه کوردوا استفاده میکنیم. حالا با دستور ng build --target=production --environment=prod --output-path cordova/www/ --base-href ./ از پروژه بیلد میگیریم به پوشه کردوا. بعد میریم داخل پوشه کوردوا cd cordova و با دستور cordova platform add browser پلتفرم وب اضافه می‌کنیم و با دستور cordova run browser اجراش می‌کنیم، اینجوری لازم نیست هردفعه برای تست اپ برای موبایل بیلد بگیریم، تو مرورگر خروجی چک میکنیم. دستورات یه دفعه دیگه مرور می‌کنیم:cordova create cordova com.hesabfun.ui &quot;Hesab Fun&quot;
ng build --target=production --environment=prod --output-path cordova/www/ --base-href ./
cd cordova
cordova platform add browser
cordova run browserبرای اضافه کردن اندروید از دستور cordova platform add android استفاده میکنیم.نکته طلایی: اگه فقط SDK ورژن ۲۶ نصب باشه با این دستور کوردوا ۷ نصب میشه که از ورژن ۴.۱ اندروید تا ۷.۱.۱ کاملا ساپورت میکنه. ولی اگه SDK نسخه‌های پایین‌تر هم رو سیستم نصب باشه از کوردوا ۶ استفاده میکنه که اپ بسیار سنگین‌تر میکنه. با این شیوه بیلدهای من برای اندروید حدودا ۸۰۰ کیلوبایت میشه.بعد نصب gradle برای اولین بیلد حتما VPN روشن کنید چون یه سری وابستگی داره برای دانلود تحریم هستیم و کار با پروکسی راه نمیفته.برای کاربران مک: بعد نصب SDK این فایل دانلود کنید و تو پوشه SDK پیست کنید تا جایگزین فایلهای قبلی بشه وگرنه خطای Android target: not installed میده.میگه JDK ورژن ۸ یا بالاتر نصب کنید، ولی اگه ۹ نصب کنید خطا میده. با دستور cordova requirements نیازمندی‌های کوردوا برای بیلد اپ متوجه میشیم و باید نصب کنیم.حالا با دستور cordova build android اپ اندروید بیلد میشه.قسمت بعدی لوگو عوض می‌کنیم و اپ سبک‌تر می‌کنیم.مثل همیشه کدها کامیت کردم، می‌تونید از اینجا تغییرات ببینید.</description>
                <category>عرفان</category>
                <author>عرفان</author>
                <pubDate>Mon, 12 Mar 2018 12:52:38 +0330</pubDate>
            </item>
                    <item>
                <title>حساب‌فان (middleware)</title>
                <link>https://virgool.io/@erfun/%D8%AD%D8%B3%D8%A7%D8%A8%D9%81%D8%A7%D9%86-middleware-s7vvkowjwqjt</link>
                <description>تو قسمت قبل لاگین نوشتیم ولی چک نکردیم توکنی که کاربر میفرسته معتبر هست یا نه، برای این کار باید سرویسی قبل روتر قرار بدیم.خوشبختانه middleware نوشتن برای gin خیلی سادس، نگاهی به فایل پایین کنید:package main

import (
   &quot;github.com/SermoDigital/jose/crypto&quot;
   &quot;github.com/SermoDigital/jose/jws&quot;
   &quot;github.com/gin-gonic/gin&quot;
   &quot;os&quot;
)

type LoginUser struct {
   Id     string
   Name   string
   Status string
   Type   string
}

// user login info
var loginUser LoginUser

func jwtAuthMiddleware() gin.HandlerFunc {
   return func(c *gin.Context) {
      rsaPublic, _ := crypto.ParseRSAPublicKeyFromPEM([]byte(os.Getenv(&quot;JWT_PUBLIC_KEY&quot;)))
      jwt, err := jws.ParseJWTFromRequest(c.Request)
      if err != nil {
         respondWithError(401, err.Error(), c)
         return
      }

      if err = jwt.Validate(rsaPublic, crypto.SigningMethodRS256); err != nil {
         respondWithError(401, err.Error(), c)
         return
      }

      temp := jwt.Claims().Get(&quot;user&quot;).(map[string]interface{})

      loginUser.Id, _ = jwt.Claims().Subject()
      loginUser.Name = temp[&quot;Name&quot;].(string)
      loginUser.Status = temp[&quot;Status&quot;].(string)
      loginUser.Type = temp[&quot;Type&quot;].(string)

      c.Next()
   }
}کد چک کردن توکن تو پست قبلی نوشته بودیم اینجا کپی کردیم، چیزی که اضافه شده مقدار loginUser هست، برای اینکه بعد لاگین ریکوئست نزنیم به سرور یا دوباره توکن باز کنیم، هرجایی از اپ میشه از این اطلاعات برای چک کردن لاگین بودن کاربر استفاده کرد. البته یه فانکشن respondWithError به هندلر اضافه کردیم تا خطاها بصورت استاندارد برگردونه:func respondWithError(code int, message string, c *gin.Context) {
   resp := map[string]string{&quot;error&quot;: message}

   c.JSON(code, resp)
   c.Abort()
}البته که فایل تست یادمون نرفته، سعی کردم بی‌خودی کدها از گیت‌هاب به اینجا کپی نکنم، ریپو کلون کنید تا تغییرات به صورت کامل ببینید.</description>
                <category>عرفان</category>
                <author>عرفان</author>
                <pubDate>Sun, 11 Mar 2018 15:55:58 +0330</pubDate>
            </item>
                    <item>
                <title>حساب‌فان (لاگین)</title>
                <link>https://virgool.io/@erfun/%D8%AD%D8%B3%D8%A7%D8%A8%D9%81%D8%A7%D9%86-%D8%A7%D8%AD%D8%B1%D8%A7%D8%B2-%D9%87%D9%88%DB%8C%D8%AA-jfv027qgdxix</link>
                <description>برای احراز هویت اپلیکیشن خودمون قرار از JWT استفاده کنیم، برای این کار میریم سراغ سایت jwt.io و لایبرری‌های گولنگ و تو نگاه اول SermoDigital/jose از همه کاملتر به نظر میرسه، داکیومنت نداره ولی فایل تست خوبی داره با یه نگاه به این تست میشه کامل درک کرد و هرچی برای ساخت توکن نیاز داریم اینجا پیدا می‌کنیم. (برای لایبرری‌های کوچیک، خوندن کد سریع‌تر از خوندن داکیومنته)برای شروع پکیج به فایل گلاید اضافه میکنیم و دستور glide update میزنیم. مرحله بعد دوتا روت برای ایجاد توکن و اعتبارسنجی توکن می‌نویسیم:package main

import &quot;github.com/gin-gonic/gin&quot;

func setupRouter() *gin.Engine {

   router := gin.Default()

   router.GET(&quot;/v1/auth/login&quot;, loginController)
   router.GET(&quot;/v1/auth/user&quot;, parseToken)
   router.GET(&quot;/&quot;, func(c *gin.Context) {
      c.JSON(200, gin.H{
         &quot;message&quot;: &quot;Hello world!&quot;,
      })
   })

   return router
}حالا فایل login.controller.go ایجاد میکنیم و دوتا فانکشن مربوطه توش می‌نویسیم:package main

import (
   &quot;github.com/SermoDigital/jose/crypto&quot;
   &quot;github.com/SermoDigital/jose/jws&quot;
   &quot;github.com/gin-gonic/gin&quot;

   &quot;strconv&quot;
   &quot;strings&quot;
   &quot;time&quot;
   &quot;os&quot;
)

var claims = jws.Claims{
   &quot;user&quot;: struct {
      Name, Email string
   }{
      Name:  &quot;ErFUN KH&quot;,
      Email: &quot;me@example.com&quot;,
   },
}

func loginController(c *gin.Context) {
   exp, _ := strconv.Atoi(os.Getenv(&quot;JWT_EXPIRATION&quot;))

   claims.SetExpiration(time.Now().Add(time.Duration(60 * 60 * 24 * time.Duration(exp) * time.Second)))
   claims.SetIssuer(os.Getenv(&quot;JWT_ISSUER&quot;))
   claims.SetAudience(os.Getenv(&quot;JWT_AUDIENCE&quot;))
   claims.SetIssuedAt(time.Now())
   claims.SetNotBefore(time.Now())
   claims.SetSubject(&quot;1&quot;) // set user id
   claims.SetJWTID(&quot;123&quot;) // set token id

   rsaPrivate, err := crypto.ParseRSAPrivateKeyFromPEM([]byte(os.Getenv(&quot;JWT_PRIVATE_KEY&quot;)))

   if err != nil {
      c.JSON(400, gin.H{&quot;message&quot;: err.Error()})
      return
   }

   jwt := jws.NewJWT(claims, crypto.SigningMethodRS256)

   token, err := jwt.Serialize(rsaPrivate)

   if err != nil {
      c.JSON(400, gin.H{&quot;message&quot;: err.Error()})
      return
   }

   c.JSON(200, gin.H{
      &quot;token&quot;: string(token),
   })
}

func parseToken(c *gin.Context) {
   rsaPublic, _ := crypto.ParseRSAPublicKeyFromPEM([]byte(os.Getenv(&quot;JWT_PUBLIC_KEY&quot;)))
   jwt, err := jws.ParseJWTFromRequest(c.Request)
   if err != nil {
      c.JSON(400, gin.H{&quot;message&quot;: err.Error()})
      return
   }

   if err = jwt.Validate(rsaPublic, crypto.SigningMethodRS256); err != nil {
      c.JSON(400, gin.H{&quot;message&quot;: err.Error()})
      return
   }

   c.JSON(200, gin.H{
      &quot;data&quot;: jwt.Claims(),
   })
}اول از همه یه شی claims ساختیم و توش نام و ایمیل کاربر وارد کردیم (فعلا هاردکد کردیم بعدا از دیتابیس می‌خونیم)، اینا دیتاهایی هستند که میخوایم سمت فرانت‌اند ازشون استفاده کنیم، بعدا بیشترش می‌کنیم ولی برای درک مطلب کافیه.مرحله بعد تابع لاگین تعریف کردیم، خط اولش از فایل .env مدت منقضی شدن توکن خوندیم، مرحله بعد تاریخ منقضی شدن برای توکن تعریف کردیم، خط بعد صادر کننده توکن معرفی کردیم که البته اینم از متغییرها خوندیم، خط بعد سرویسی که قراره از این توکن استفاده کنه و همینطور به ترتیب: تاریخ ایجاد توکن، زمان امکان استفاده توکن بعد اون آیدی یوزر و آیدی توکن فعلا به صورت هاردکد تعریف کردیم تا بعدا درستش کنیم.خط بعدی کلید خصوصی از فایل .env خوندیم و به لایبرری معرفی کردیم، بعد اون اگر خطایی نداد با استفاده از دیتاهایی که داشتیم و کلید خصوصی شئ jwt ایجاد می‌کنیم، بعد اون از روی شئ jwt توکن ایجاد می‌کنیم و برای کاربر ارسال می‌کنیم.خب تابع بعدی اول کلید عمومی خوندیم بعد سعی کردیم توکن از هدر بگیریم، شما باید توکن به این صورت تو هدر ریکوئست وارد کنید:Authorization: Bearer YOUR_TOKENبعد اون سعی کردیم توکن با کلید عمومی چک کنیم تا مطمئن بشیم خودمون ایجادش کردیم، حالا اگه مشکلی نبود اطلاعات توکن باز میکنیم و برای خودمون برمی‌گردونیم تا مطمئن بشیم کد به درستی کار میکنه.یادمون نمیره متغییرهای جدید به فایل .env اضافه کنیم:# JWT config
JWT_PRIVATE_KEY=&#039;-----BEGIN RSA PRIVATE KEY-----\nMIIEpQIBAAKCAQEA2wo4ugkqLamtl+6AxgCg86qAkuVGsh/dQ7SJ1Kb0sLOFrfsB\nF8SKagQB3JkwB2cTRzD6WyvcvFEGLo4KJAa/U9xfMlyCyMCDRCeabCzNsNPKOL5t\nEW8120X0AERc1g9mRQGwr/uMsBmlwGzutqutB/CXKL7sIw0yynJzlltTI+ISGMcb\nB8aDAOSSuR1hcZCCk3Jaat9M2DdK4/lYmVO/IFYdGgYLlItRqHExKmZInJASHIur\nquxgI72OVq4jXhVCARBlLaQa25oPWcp94+OgTS0wYvQl1aE9H9rok9bSd/YES3kL\nkH3DdjNRPzSRRCX+J8CxUFZw6wFXsVmpCtEM3QIDAQABAoIBADrDXUCboNMrSEUQ\nWT/Ff2iff2rpU7QJ1GSLlMaWG+Mj5mMsibiEo9WZSZ6TAk2aG5Pn0eKPu+JRomTu\n+k17+exXnLp4EyYkb5LjRQxsYKplx0S94ajhuwMemz1PGdDbxMYSlAJCbBX6a3ta\nPhiHqh4NL6BgyB0HN28UkWnvCjj/uFUz6qgJQFZMs1qx7LRHhPkPIFx/GVNng8qz\nEw7pXsgMuTysOmuBO97By95eDqNYNJYqQwGodIhG7zxbyxXGnT3xwOoXN2L68XpR\npQh5Nl5BgpPLOirJUW+O2abUi/i+bHOIozS9DKyGtm+20yiCSIOzEcnZ0rdMQDYw\nL4W+wMECgYEA9Rdg1R5lJF/+uLsuk87rsLEt5bWiqEleihGf/qDkurOe1trbnlZ2\nYBHEuObykJSq4wZpM0zOyjycNYfQ/zteuxnEEc+NIYdmN3KGxflL5jBAEjyYQo4a\nNL57MQY53nPSosMY0oRHj6AzCMRplMQpmu+CGUL31d+YFOXGHNMEy8kCgYEA5MoC\nIQuoIKMYjkH/leU14YNkB/oOk5LEB+5opLHAYo0a9wSkE7q5MYmpX7ZdBOsNsRre\ndNDqy9pTLfPj7zNQ8G3DVFSujgQsjnjDp7HGTs+c0eSUAQLtNsk20F5gXtdYRqA1\nuZh0CMXKMSXUWbmYQXgVR3Y9I0E9wwHZ/rFBmnUCgYEAuPevyKdrxYv8/QWnHT3o\neiz9aoMuArt8cc7jZJOgi5bLpXL+k/zE0bQXN0R0g9DvNu67rk+lMNOVQIEDpdv0\nnlfPtXFiHY/GAMqaFAcU1OBNOnYoovIDrRKkflcojU30BYofzaCvMSHB4jf5RqDU\nlW10TgRQbkSUzhCq9036LKECgYEAgJ9AyysufgqzB2b7NV4DCKFBX2qpPzXHl13k\n3pI/wifp/O1TAPR8oOjvm6t+aAFtVR/x6GJ7XdeD49W1UwjafBB5O7PP3m9iTUZ/\nWIuNHUmCtE15F4h5q887TbGBJFCUhEAVdB3NPhFUNoU5+KdqfYPxEpfajzNicXtc\n/t7QLvECgYEAtwKadCXM9VpbN71qYG2pxtPi2qxWnnnBFLhxHJzY3Ra70o9I/Cfg\nI9CCRmLeAMjraWprv14P0tscAvrPrfUw5o9No0t4W6bWY/ibTxM1Op0/kbLxkJbg\n5BaLj9jZ7CzLGlZkxWg1G7lqLT8wz0gznuspueqcz7ZdstW1NDZogCc=\n-----END RSA PRIVATE KEY-----&#039;
JWT_PUBLIC_KEY=&#039;-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA2wo4ugkqLamtl+6AxgCg\n86qAkuVGsh/dQ7SJ1Kb0sLOFrfsBF8SKagQB3JkwB2cTRzD6WyvcvFEGLo4KJAa/\nU9xfMlyCyMCDRCeabCzNsNPKOL5tEW8120X0AERc1g9mRQGwr/uMsBmlwGzutqut\nB/CXKL7sIw0yynJzlltTI+ISGMcbB8aDAOSSuR1hcZCCk3Jaat9M2DdK4/lYmVO/\nIFYdGgYLlItRqHExKmZInJASHIurquxgI72OVq4jXhVCARBlLaQa25oPWcp94+Og\nTS0wYvQl1aE9H9rok9bSd/YES3kLkH3DdjNRPzSRRCX+J8CxUFZw6wFXsVmpCtEM\n3QIDAQAB\n-----END PUBLIC KEY-----&#039;
JWT_EXPIRATION=7                    #token life 7 days
JWT_ISSUER=hesabfun.com
JWT_AUDIENCE=https://hesabfun.comبرای تست API از  insomnia استفاده می‌کنیم، برای همه پلتفرم‌ها موجوده، کافیه لاگین کنید تا از اندپوینت‌ها بکآپ بگیره، اینوایرمنت داره و...خب تا اینجا توکن ایجاد می‌کنیم و با ارسال دوباره به اند‌پوینت بعدی چک می‌کنیم معتبر هست یا نه ولی نیاز داریم فقط با یوزر و پسورد معتبر توکن ایجاد بشه، پس میریم سراغ دیتابیس:package main

import (
   &quot;github.com/SermoDigital/jose/crypto&quot;
   &quot;github.com/SermoDigital/jose/jws&quot;
   &quot;github.com/gin-gonic/gin&quot;
   &quot;github.com/gin-gonic/gin/binding&quot;
   &quot;upper.io/db.v3&quot;

   &quot;os&quot;
   &quot;strconv&quot;
   &quot;time&quot;
)

var claims jws.Claims

func loginController(c *gin.Context) {
   var request struct {
      Mobile   string `json:&quot;mobile&quot; binding:&quot;required,gte=10,lte=12&quot;`
      Password string `json:&quot;password&quot; binding:&quot;required,gte=0,lte=255&quot;`
   }

   if err := c.ShouldBindWith(&amp;request, binding.JSON); err != nil {
      c.JSON(400, gin.H{&quot;message&quot;: err.Error()})
      return
   }

   var user User
   err := MySql.Collection(&quot;users&quot;).Find(db.Cond{
      &quot;mobile&quot;:   request.Mobile,
      &quot;password&quot;: GetMD5Hash(request.Password),
   }).Where(&quot;status IN (&#039;active&#039;, &#039;pending&#039;)&quot;).One(&amp;user)
   if err != nil {
      c.JSON(400, gin.H{&quot;message&quot;: &quot;Mobile or password incorrect&quot;})
      return
   }

   claims = jws.Claims{
      &quot;user&quot;: struct {
         Name, Status, Type string
      }{
         Name:   user.Name,
         Status: user.Status,
         Type:   user.Type,
      },
   }

   token, err := generateToken(user.ID)
   if err != nil {
      c.JSON(400, gin.H{&quot;message&quot;: err.Error()})
      return
   }

   c.JSON(200, gin.H{
      &quot;token&quot;: token,
   })
}

func parseToken(c *gin.Context) {
   rsaPublic, _ := crypto.ParseRSAPublicKeyFromPEM([]byte(os.Getenv(&quot;JWT_PUBLIC_KEY&quot;)))
   jwt, err := jws.ParseJWTFromRequest(c.Request)
   if err != nil {
      c.JSON(400, gin.H{&quot;message&quot;: err.Error()})
      return
   }

   if err = jwt.Validate(rsaPublic, crypto.SigningMethodRS256); err != nil {
      c.JSON(400, gin.H{&quot;message&quot;: err.Error()})
      return
   }

   c.JSON(200, gin.H{
      &quot;data&quot;: jwt.Claims(),
   })
}

func generateToken(userId uint) (string, error) {
   exp, _ := strconv.Atoi(os.Getenv(&quot;JWT_EXPIRATION&quot;))

   claims.SetExpiration(time.Now().Add(time.Duration(60 * 60 * 24 * time.Duration(exp) * time.Second)))
   claims.SetIssuer(os.Getenv(&quot;JWT_ISSUER&quot;))
   claims.SetAudience(os.Getenv(&quot;JWT_AUDIENCE&quot;))
   claims.SetIssuedAt(time.Now())
   claims.SetNotBefore(time.Now())
   claims.SetSubject(strconv.FormatUint(uint64(userId), 10)) // set user id
   claims.SetJWTID(&quot;123&quot;)                                    // set token id

   rsaPrivate, err := crypto.ParseRSAPrivateKeyFromPEM([]byte(os.Getenv(&quot;JWT_PRIVATE_KEY&quot;)))

   if err != nil {
      return &quot;&quot;, err
   }

   jwt := jws.NewJWT(claims, crypto.SigningMethodRS256)

   token, err := jwt.Serialize(rsaPrivate)

   if err != nil {
      return &quot;&quot;, err
   }

   return string(token), nil
}خیلی خب، کنترولر لاگین یکم تغییر دادیم، اول ولیدیتور تعریف کردیم تا مطمئن باشیم کاربر فیلدهای مربوطه پر کرده، بعد اون کوئری زدیم به دیتابیس اگر کاربری با این پسورد وجود داشته باشه و فعال باشه توکن ایجاد کنه. همچنین تست نوشتیم و یه هندلر برای ساخت MD5 ولی هنوز کارمون با این کنترولر تموم نشده، بعدا کمی تغییرش میدیم و بهترش می‌کنیم.لاگین و دریافت توکنقسمت بعد میدلور برای احراز هویت کاربران می‌نویسیم.تمام کدها رو گیت‌هاب هست، می‌تونید بررسی یا حتی کاملترش کنید :)</description>
                <category>عرفان</category>
                <author>عرفان</author>
                <pubDate>Sat, 10 Mar 2018 23:28:07 +0330</pubDate>
            </item>
                    <item>
                <title>احراز هویت (JWT, JWS, JWE)</title>
                <link>https://virgool.io/@erfun/%D9%87%D9%88%DB%8C%D8%AA-%D8%B3%D9%86%D8%AC%DB%8C-%DA%A9%D8%A7%D8%B1%D8%A8%D8%B1%D8%A7%D9%86-jwt-jws-jwe-pedif3sejkol</link>
                <description>اول از همه بگم: JWT مخفف JSON Web Token و JWS مخفف JSON Web Signature و JWE مخفف JSON Web Encryption هست.تو فارسی به jwt می‌گم جوت تا تایپ و تلفظ‌ش ساده باشه. یه جاهایی جمله با jwt شروع میشه و ویرگول نوشته چپ به راست میکنه، مجبورم.دو روش کلی و پرکاربرد اعتبارسنجی سمت سرور، برای برنامه‌های سمت کاربر وب وجود دارند:روش اول Cookie-Based Authentication که پرکاربردترین روش بوده و در این حالت به ازای هر درخواست، یک کوکی جهت اعتبارسنجی کاربر به سمت سرور ارسال می‌شود (و برعکس).روش دوم Token-Based Authentication که بر مبنای ارسال یک توکن امضا شده به سرور، به ازای هر درخواست است.مزیت‌های استفاده‌ی از روش مبتنی بر توکن چیست؟دامنه‌های متفابل Cross-domain / CORS: کوکی‌ها و CORS آنچنان با هم سازگاری ندارند، چون صدور یک کوکی وابسته‌ است به دومین مرتبط به آن و استفاده‌ از آن در سایر دومین‌ها عموما پذیرفته شده نیست، اما روش مبتنی بر توکن، وابستگی به دومین صدور آن‌ را ندارد و اصالت آن بر اساس روش‌های رمزنگاری تصدیق می‌شود.بدون حالت بودن و مقیاس پذیری سمت سرور: در حین کار با توکن‌ها، نیازی به ذخیره‌ اطلاعات داخل سشن سمت سرور نیست و توکن موجودیتی است خود شمول (self-contained). به این معنا که حاوی تمام اطلاعات مرتبط با کاربر بوده و محل ذخیره‌ی آن در local storage و یا کوکی سمت کاربر می‌باشد. (البته اگر نیاز به مدیریت توکن‌ها داشته باشید نباید این مورد درنظر بگیرید)توزیع برنامه با CDN: حین استفاده از روش مبتنی بر توکن، امکان توزیع تمام فایل‌های برنامه (جاوا اسکریپت، تصاویر و غیره) توسط CDN وجود دارد و در این حالت کدهای سمت سرور، تنها یک API ساده خواهد بود.عدم در هم تنیدگی کدهای سمت سرور و کلاینت: در حالت استفاده‌ از توکن، این توکن می‌تواند از هرجایی و هر برنامه‌ای صادر شود و در این حالت نیازی نیست تا وابستگی ویژه‌ای بین کدهای سمت کلاینت و سرور وجود داشته باشد.سازگاری بهتر با سیستم‌های موبایل: در حین توسعه‌ برنامه‌های بومی پلتفرم‌های مختلف موبایل، کوکی‌ها روش مطلوبی جهت کار با APIهای سمت سرور نیستند. تطابق یافتن با روش‌های مبتنی بر توکن در این حالت ساده‌تر است.باگ CSRF: از آنجایی که از کوکی استفاده نمی‌شود، نیازی به نگرانی در مورد حملات CSRF نیست. چون دیگر برای مثال امکان سو استفاده‌ از کوکی فعلی اعتبارسنجی شده، جهت صدور درخواست‌هایی با سطح دسترسی شخص لاگین شده وجود ندارد، چون این روش کوکی را به سمت سرور ارسال نمی‌کند.کارآیی بهتر: حین استفاده‌ از توکن‌ها، به علت ماهیت خود شمول آنها، رفت و برگشت کمتری به بانک اطلاعاتی صورت گرفته و سرعت بالاتری را شاهد خواهیم بود. (البته اگر نیاز به مدیریت توکن‌ها داشته باشید نباید این مورد درنظر بگیرید)امکان نوشتن آزمون‌های یکپارچگی ساده‌تر: در حالت استفاده‌ از توکن‌ها آزمودن یکپارچگی برنامه نیازی به رد شدن از صفحه‌ی لاگین را ندارد و پیاده سازی این نوع آزمون‌ها ساده‌تر از قبل است.استاندارد بودن: امروزه همینقدر که استاندارد JSON Web Token را پیاده سازی کرده باشید، امکان کار با انواع و اقسام پلتفرم‌ها و کتابخانه‌ها را خواهید یافت.اما JWT یا JSON Web Token چیست؟ توضیح: JSON Web Token یا JWT یک استاندارد وب است (RFC 7519) که روشی فشرده و خود شمول (self-contained) را جهت انتقال امن اطلاعات بین مقاصد مختلف را توسط یک شی JSON تعریف می‌کند. این اطلاعات قابل تصدیق و اطمینان هستند، از این‌ رو که به صورت دیجیتال امضا می‌شوند. JWTها توسط یک کلید خصوصی (با استفاده از الگوریتم HMAC) و یا یک جفت کلید خصوصی و عمومی (توسط الگوریتم RSA) قابل امضا شدن هستند.در این تعریف واژه‌هایی مانند «فشرده» و «خود شمول» بکار رفته‌اند: - «فشرده بودن»: اندازه‌ی شی JSON یک توکن در این حالت کوچک بوده و به سادگی از طریق یک URL و یا پارامترهای POST و یا داخل یک HTTP Header قابل ارسال است و به دلیل کوچک بودن این اندازه انتقال آن نیز سریع است. - «خود شمول»: بار مفید (payload) این توکن شامل تمام اطلاعات مورد نیاز جهت اعتبارسنجی یک کاربر است تا دیگر نیازی به کوئری گرفتن هر باره‌ از بانک اطلاعاتی نباشد (در این روش مرسوم است که فقط یکبار از بانک اطلاعاتی کوئری گرفته شده و اطلاعات مرتبط با کاربر را امضای دیجیتال کرده و به سمت کاربر ارسال می‌کنند).جوت‌ها دارای سه قسمت است و این قسمت‌ها با نقطه از هم جا شدند، مانند: xxxxx.yyyyy.zzzzz که شامل سه بخش: header.payload.signature می‌باشند و هر بخش با الگوریتم Base64 اینکد می‌شود. نمونه‌ای از یک توکن جوت:eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.XbPfbIHMI6arZ3Y922BhjWgQzWXcXNrz0ogtVhfEd2oبخش Header:بخش Header عموما دارای دو قسمت است که نوع توکن و الگوریتم مورد استفاده‌ توسط آن را مشخص می‌کند. نوع توکن در اینجا JWT است و الگوریتم‌های مورد استفاده، عموما  HMAC SHA256 و یا RSA هستند. نمونه اطلاعات دیکد شده بخش هدر:{
   &quot;alg&quot;: &quot;RS256&quot;,
   &quot;typ&quot;: &quot;JWT&quot;
}بخش payload:بخش payload یا «بار مفید» توکن، شامل claims است، منظور از claims اطلاعاتی است در مورد موجودیت مدنظر (عموما کاربر) و یک سری متادیتای اضافی. سه نوع claim وجود دارند:نوع اول Reserved claims: یک سری اطلاعات مفید و از پیش تعیین شده‌ غیراجباری هستند مانند:iss یا صادر کنند، exp یا تاریخ انقضا، sub یا عنوان (subject) و aud یا مخاطب (audience)نوع دوم Public claims: می‌تواند شامل اطلاعاتی باشد که توسط IANA JSON Web Token Registry پیشتر ثبت شده‌است و فضاهای نام آنها تداخلی نداشته باشند.نوع سوم Private claims: ادعای سفارشی هستند که جهت انتقال داده‌ها بین مقاصد مختلف مورد استفاده قرار می‌گیرند.نمونه اینکد شده بخش payload توکن:{
   &quot;exp&quot;: 1520507268,
   &quot;sub&quot;: 582946,
   &quot;name&quot;: &quot;ErFUN KH&quot;,
   &quot;admin&quot;: true
}در نمونه بالا دو دیتای اول از نوع Reserved claims می‌باشد و دو دیتای آخر از نوع Private claims.به طور استاندارد بخش Reserved claims می‌تونه شامل این موارد باشه:فیلد iss یا issuer: صادر کننده توکن در این قسمت مشخص میشه، برای مثال مقدارش می‌تونه &quot;hesabfun.com&quot; باشه.فیلد sub یا subject: در اینجا موضوع اصلی توکن مطرح میشه، مثلا موضوع توکن ما شناسایی کاربرانه، پس یوزر آیدی کاربر توش قرار میدیم تا متوجه بشیم کسی که توکن برامون ارسال کرده کیه.فیلد aud یا audience: در این قسمت مشخص می‌کنیم این توکن باید کجا مورد استفاده قرار بگیره، این مواقعی کاربرد داره که شما چندتا سرور داشته باشید و همه از یک کلید خصوصی برای امضا استفاده می‌کنند. برای مثال مقدارش می‌تونه &quot;https://blog.hesabfun.com&quot; باشه.فیلد exp یا expiration: در این قسمت مشخص میکنیم توکن تا چه زمانی اعتبار داره، این تاریخ بصورت Unix time مشخص می‌کنیم.فیلد nbf یا not before: در این قسمت مشخص می‌کنیم توکن از چه تاریخی به بعد باید مورد مورد پردازش قرار بگیره، یعنی ممکنه توکن زودتر ایجاد کنیم ولی فعلا اجازه استفاده از اون نداشته باشند.فیلد iat یا issuedAt: تاریخ ایجاد توکن به صورت Unix time اینجا قرار میدیم، بیشتر برای اینکه متوجه بشیم توکن جدیده یا نه استفاده میشه.فیلد jti یا jwt id: آیدی منحصر به فرد برای هر توکن، اگه سیستم مدیریت توکن تلگرام دیده باشید متوجه میشید میتونه همچین استفاده‌ای داشته باشه، بهتره برای بلاک کردن، کل توکن تو دیتابیس قرار ندید و به هرکدوم یه آیدی بدید اینجوری دیتابیس سبک‌تری دارید.این مقادیر همینجا تموم نمیشه، میتونید نگاهی به لیست مایکروسافت کنید، ولی بهتره خیلی شلوغش نکنید، مهم همین‌ها هستند که خیلی وقت‌ها استفاده نمیشن نهایتا sub و exp استفاده می‌کنند.بخش signature: تا اینا دیدیم همه دیتاهای با الگوریتم بیس۶۴ اینکد شد که به راحتی تو هرسیستمی دیکد میشه (اگه با این شیوه آشنا نیستید باید بگم فقط کاراکترهای غیر مجاز به حروف تبدیل میکنه، مقاله ویکی‌پدیا بخونید) و هرکسی می‌تونه همچین توکنی بسازه و برای ما ارسال کنه ولی نه با وجود بخش سوم (امضا).همونطوری که می‌بینید بخش اول (هدر) با بخش دوم (پیلود) جمع شده و بعد با کلید خصوصی (کلید خصوصی فقط در سرور موجوده) رمز میشه، این به عنوان بخش سوم یا امضا توکن ما استفاده میشه، درصورتی که اطاعات پیلود دست کاری بشه امضا برای سرور معتبر نیست، همچنین چون کاربران کلید خصوصی ندارند نمی‌تونند خودشون توکن تولید کنند.همینطور درنظر داسته باشید اطلاعات حساس مثل پسورد یا... داخل توکن نذارید چون هرکسی می‌تونه به سادگی توکن باز کنه و دیتای داخلش بخونه، ولی اگر واقعا نیازه اطلاعات حساس داخلش بذارید باید از JWE استفاده کنید.چگونه از JWT در برنامه‌ها استفاده می‌شود؟ زمانیکه کاربر، لاگین موفقی را به سیستم انجام می‌دهد، یک توکن امن توسط سرور صادر شده و با فرمت JWT به سمت کلاینت ارسال می‌شود. این توکن باید به صورت محلی در سمت کاربر ذخیره شود. عموما از local storage برای ذخیره‌ی این توکن استفاده می‌شود اما استفاده‌ی از کوکی‌ها نیز منعی ندارد (ولی از لحاظ امنیتی خیلی خوب نیست، پایین‌تر توضیح میدم چرا). بنابراین دیگر در اینجا سشنی در سمت سرور به ازای هر کاربر ایجاد نمی‌شود و کوکی سمت سروری به سمت کلاینت ارسال نمی‌گردد.سپس هر زمانیکه کاربری قصد داشت به یک صفحه یا محتوای محافظت شده دسترسی پیدا کند، باید توکن خود را به سمت سرور ارسال نماید. عموما اینکار توسط یک header سفارشی Authorization به همراه Bearer schema صورت می‌گیرد و یک چنین شکلی را دارد:Authorization: Bearer &lt;token&gt;این روش اعتبارسنجی، بدون حالت (stateless) است، از این جهت که وضعیت کاربر هیچگاه در سمت سرور ذخیره نمی‌گردد. API سمت سرور ابتدا به دنبال هدر Authorization فوق در درخواست دریافتی می‌گردد، اگر یافت شد و اصالت آن تایید شد کاربر امکان دسترسی به منبع محافظت شده را پیدا می‌کند. نکته‌ی مهم اینجا است که چون این توکن‌ها «خود شمول» هستند و تمام اطلاعات لازم جهت اعطای دسترسی‌های کاربر به او در آن وجود دارند، دیگر نیازی به رفت و برگشت به بانک اطلاعاتی جهت تایید این اطلاعات تصدیق شده نیست. به همین جهت کارآیی و سرعت بالاتری را نیز به همراه خواهند داشت.نگاهی به محل ذخیره سازی JWT و نکات مرتبط با آن محل متداول ذخیره‌ JWT ها در local storage مرورگرها است و در اغلب سناریوها نیز به خوبی کار می‌کند. فقط باید دقت داشت که local storage یک sandbox است و محدود به دومین جاری برنامه و از طریق برای مثال زیر دامنه‌های آن قابل دسترسی نیست. در این حالت می‌توان JWT را در کوکی‌های ایجاد شده‌ در سمت کاربر نیز ذخیره کرد که چنین محدودیتی را ندارند. اما باید دقت داشت که حداکثر اندازه‌ی حجم کوکی‌ها 4 کیلوبایت است و با افزایش claims ذخیره شده‌ی در یک JWT و انکد شدن آن، این حجم ممکن است از 4 کیلوبایت بیشتر شود. بنابراین باید به این نکات دقت داشت.امکان ذخیره سازی توکن‌ها در session storage مرورگرها نیز وجود دارد. session storage بسیار شبیه است به local storage اما به محض بسته شدن مرورگر پاک می‌شود.اگر از local storage استفاده می‌کنید حملات Cross Site Request Forgery در اینجا دیگر موثر نخواهند بود، اما اگر به حالت استفاده‌ی از کوکی‌ها برای ذخیره‌ی توکن‌ها سوئیچ کنید این مساله همانند قبل خواهد بود و مسیر است، در این حالت بهتر است طول عمر توکن‌ها را تاحد ممکن کوتاه تعریف کنید تا اگر اطلاعات آن‌ها فاش شد به زودی بی‌مصرف شوند. انقضا و صدور مجدد توکن‌ها به چه صورتی است؟ توکن‌های بدون حالت صرفا بر اساس بررسی امضای پیام رسیده کار می‌کنند. به این معنا که یک توکن می‌تواند تا ابد معتبر باقی بماند. برای رفع این مشکل باید exp یا تاریخ انقضای متناسبی را به توکن اضافه کرد. برای برنامه‌های حساس این عدد می‌تواند 15 دقیقه باشد و برای برنامه‌های کمتر حساس، چندین ماه.اما اگر در این بین قرار به ابطال سریع توکنی بود چه باید کرد؟ (مثلا کاربری را در همین لحظه غیرفعال کردید)یک راه حل آن، ثبت رکورد‌های تمام توکن‌های صادر شده در بانک اطلاعاتی است. برای این منظور می‌توان یک از فیلد jti کرد. این idها در بانک اطلاعاتی ذخیره می‌کنیم. به این ترتیب می‌توان بین توکن‌های صادر شده و کاربران و اطلاعات به روز آن‌ها ارتباط برقرار کرد. در این حالت برنامه علاوه بر بررسی امضای توکن می‌تواند به لیست idهای صادر شده و ذخیره شده‌ی در دیتابیس نیز مراجعه کرده و اعتبارسنجی اضافه‌تری را جهت باطل کردن سریع توکن‌ها انجام دهد. هرچند این روش دیگر آنچنان stateless نیست، اما با دنیای واقعی سازگاری بیشتری دارد.حداکثر امنیت JWTها را چگونه می‌توان تامین کرد؟ - تمام توکن‌های خود را با یک کلید قوی امضا کنید و این کلید تنها باید بر روی سرور ذخیره شده باشد. هر زمانیکه سرور توکنی را از کاربر دریافت می‌کند، این سرور است که باید کار بررسی اعتبار امضای پیام رسیده را بر اساس کلید قوی خود انجام دهد.- اگر اطلاعات حساسی را در توکن‌ها قرار می‌دهید، باید از JWE یا JSON Web Encryption استفاده کنید، زیرا JWTها صرفا دارای امضای دیجیتال هستند و نه اینکه رمزنگاری شده باشند. - بهتر است توکن‌ها را از طریق ارتباطات غیر HTTPS، ارسال نکرد.- اگر از کوکی‌ها برای ذخیره سازی آن‌ها استفاده می‌کنید، از Secure استفاده کنید تا از Cross-Site Scripting XSS attacks در امان باشید.- مدت اعتبار توکن‌های صادر شده را منطقی انتخاب کنید.بیشتر این مطلب کپی از این سه صفحه بود: یک، دو، سه. ولی چون هیچ مطلبی پیدا نکردم اونطور که میخوام توضیح داده باشه ۳ تاش باهم قاطی کردم و بعضی بخش‌ها حذف یا اضافه کردم، درضمن این مطلب انگلیسی هم فوق العاده ساده توضیح داده و بیشتر عمیق شده، خوندنش خالی از لطف نیست.</description>
                <category>عرفان</category>
                <author>عرفان</author>
                <pubDate>Thu, 08 Mar 2018 23:18:08 +0330</pubDate>
            </item>
                    <item>
                <title>حساب‌فان (بریم رو گیت‌هاب)</title>
                <link>https://virgool.io/@erfun/%D8%AD%D8%B3%D8%A7%D8%A8%D9%81%D8%A7%D9%86-%D8%A8%D8%B1%DB%8C%D9%85-%D8%B1%D9%88-%DA%AF%DB%8C%D8%AA%D9%87%D8%A7%D8%A8-er86bk1cukel</link>
                <description>فکر کنم کار با گیت‌لب یاد گرفتید ولی دیگه نیازی نیست با گیت‌لب کار کنیم، گیت‌هاب برای پروژه‌های اپن سورس جای بهتریه ولی خب برای نسخه پرایوتش پولیه، واس همین پروژه‌های پرایوت می‌برم رو گیت‌لب.واضحه اول یه ریپازیتوری رو گیت‌هاب درست می‌کنیم، مرحله دوم کدها کامیت می‌کنیم، بعدش میریم سراغ تست :)برای تست، کدکاوریج و... میتونید از این لینک نگاهی به ابزارهایی که برای گیت‌هاب وجود داره بکنید، برای CI از circleci استفاده می‌کنم، چون فوق‌العاده سریع و سادس، با یه نگاه کوچیک به داکیومنتش متوجه میشید چی به چیه، برای همه زبان‌ها مثال داره اگه مثال‌ها درک نکردید بهتره پست من درباره گیت‌لب بخونید.برای ایجاد تست اول سری به سایتش بزنید، لاگین کنید و در مرحله بعد فولدر .circleci در روت پروژه درست کنید و داخل این فولدر فایل .config.yml بسازید، حالا محتوای این فایل برای پروژه ما به این صورت میشه:# Golang CircleCI 2.0 configuration file
#
# Check https://circleci.com/docs/2.0/language-go/ for more details
version: 2
jobs:
  build:
    docker:
      # specify the version
      - image: circleci/golang:latest
        environment:
          - MYSQL_ADDRESS=localhost
          - MYSQL_DATABASE=hesab_fun
          - MYSQL_USERNAME=root
          - MYSQL_PASSWORD=123456
          - MYSQL_PORT=3306
      - image: mysql:latest
        environment:
          - MYSQL_DATABASE=hesab_fun
          - MYSQL_ROOT_PASSWORD=123456

      # Specify service dependencies here if necessary
      # CircleCI maintains a library of pre-built images
      # documented at https://circleci.com/docs/2.0/circleci-images/
      # - image: circleci/postgres:9.4

    #### TEMPLATE_NOTE: go expects specific checkout path representing url
    #### expecting it in the form of
    ####   /go/src/github.com/circleci/go-tool
    ####   /go/src/bitbucket.org/circleci/go-tool
    working_directory: /go/src/github.com/hesabFun/core
    steps:
      - checkout

      # specify any bash command here prefixed with `run: `
      - run: curl https://glide.sh/get | sh
      - run: glide install
      - run: go get github.com/rubenv/sql-migrate/...
      - run: sql-migrate up
      - run: sql-migrate up -env=seed
      - run: cp -r .env.example .env
      - run: go build
      - run: go test -v ./...  -race -coverprofile=coverage.txt -covermode=atomic
      - run: bash &lt;(curl -s https://codecov.io/bash) -t ${CODECOV_TOKEN}همه چیز واضحه بجز خط آخر، اون برای کدکاوریج پروژه‌س، برای مثال نگاهی به این ریپورت بندازید، میگه چند درصد پروژه تست شده و کدوم خط‌ها تست نشدن، اگه مشکلی برای اون قسمت از پروژه اتفاق بیفته، تو تست متوجه نمی‌شیم.برای کدکاوریج نیازه اول داخل سایتش لاگین کنید بعد عین من خط آخر به فایل تست اضافه کنید و تمام.این دوتا سرویس خیلی مهم بودن تا بدونیم پروژه در چه حاله ولی سرویس‌های دیگم هستند اگه حوصله داشتید اضافه کنید، برای مثال من گو ریپورت اضافه کردم، لاگین نمی‌خواد، فقط لینک پروژه گیت‌هاب بهش بدید.حالا من بَدج این سرویس‌ها به فایل README.md پروژه اضافه کردم تا با باز کردن ریپو وضعیت تست، کدکاوریج و... ببینید. (دیگه خبری از اسلک و تلگرام نیست)# HeasbFun - core
[![Build Status](https://circleci.com/gh/hesabFun/core.svg?&amp;style=shield)](https://circleci.com/gh/hesabFun/core)
[![codecov](https://codecov.io/gh/hesabFun/core/branch/master/graph/badge.svg)](https://codecov.io/gh/hesabFun/core)
[![Go Report](https://goreportcard.com/badge/github.com/hesabFun/core)](https://goreportcard.com/report/github.com/hesabFun/core)
[![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](https://github.com/hesabFun/core/blob/master/LICENSE)

Free, open source and cross-platform finance applicationپروژه اپن سورسه، کافیه از اینجا کلون کنید تا تموم کامیت‌ها ببینید.</description>
                <category>عرفان</category>
                <author>عرفان</author>
                <pubDate>Tue, 06 Mar 2018 13:53:38 +0330</pubDate>
            </item>
                    <item>
                <title>حساب‌فان (دیتابیس و متعلقات)</title>
                <link>https://virgool.io/@erfun/%D8%AD%D8%B3%D8%A7%D8%A8%D9%81%D8%A7%D9%86-%D8%AF%DB%8C%D8%AA%D8%A7%D8%A8%DB%8C%D8%B3-%D9%88-%D9%85%D8%AA%D8%B9%D9%84%D9%82%D8%A7%D8%AA-ngopvlmxhenw</link>
                <description>حالا قصد داریم دیتابیس به پروژه اضافه کنیم، برای این کار به ORM و migration و seed نیاز داریم.همونطور که تو پست مربوط به بک‌اند توضیح دادم از upper-db استفاده می‌کنم، چون خیلی سادس، داکیومنت خوبی داره، برای اکثر دیتابیس‌های ریلیشن، آداپتر داره و...برای مایگریشن و سید از rubenv/sql-migrate استفاده میکنم چون فوق العاده سادس، استاندارده، چیز اضافه نداره و قابل اعتماده.پس برای اضافه کردن پکیج دیتابیس فایل glide.yaml به این صورت تغییر میدیم:package: gitlab.com/erfun/hesabFun
homepage: https://hesabfun.com

owners:
- name: ErFUN KH
  email: erfun.kh@gmail.com
  homepage: https://erfun.net

import:
- package: github.com/gin-gonic/gin

# testing package
- package: github.com/stretchr/testify

# ORM
- package: github.com/go-sql-driver/mysql
- package: upper.io/db.v3/mysqlو فایل main.go به این صورت تغییر میدیم:package main

import (
   &quot;upper.io/db.v3/mysql&quot;
   &quot;upper.io/db.v3/lib/sqlbuilder&quot;
   &quot;os&quot;
   &quot;log&quot;
)

var settings = mysql.ConnectionURL{
   Database:  &quot;YOUR_MYSQL_DATABASE&quot;,
   Host:          &quot;YOUR_MYSQL_ADDRESS&quot;,
   User:          &quot;YOUR_MYSQL_USERNAME&quot;,
   Password: &quot;YOUR_MYSQL_PASSWORD&quot;,
}
var MySql sqlbuilder.Database

func main() {

   var DBError error
   MySql, DBError = mysql.Open(settings)
   if DBError != nil {
      log.Fatal(&quot;MySQL Error: &quot;, DBError)
   }
   MySql.SetLogging(false)
   defer MySql.Close()

   router := setupRouter()

   router.Run() // listen and serve on 0.0.0.0:8080
}خیلی خوب، الآن اپلیکیشن به دیتابیس MySQL وصل میشه، ولی همونطور که می‌بینید تمام اطلاعات دیتابیس از جمله یوزر و پسورد تو کد نوشته شده، قرار نیست همینطور بمونه چون اگر تیم داشته باشید ممکنه یوزر و پسورد دیتابیس هرکسی رو سیستم شخصیش فرق داشته باشه، همچنین دلیلی نداره یوزر و پسورد دیتابیس اصلی به همه دولوپرها بدید، از همه مهمتر برای تست به مشکل می‌خوریم چون قراره دیتابیس حسابی کثیف کنیم.منم مثل همه از متغییرهای سیستم استفاده میکنم، با استفاده از پکیج goDotEnv یه فایل .env میسازیم و هردفعه اطلاعاتش میخونیم و رو سیستم ست میکنیم.بنابراین پکیج به پکیج‌منیجر اضافه میکنیم:package: gitlab.com/erfun/hesabFun
homepage: https://hesabfun.com

owners:
- name: ErFUN KH
  email: erfun.kh@gmail.com
  homepage: https://erfun.net

import:
- package: github.com/gin-gonic/gin

# testing package
- package: github.com/stretchr/testify

# ORM
- package: github.com/go-sql-driver/mysql
- package: upper.io/db.v3/mysql

# Load environment variables from `.env`
- package: github.com/joho/godotenvهمچنین جهت لود متغییرها پکیج goDotEnv لود میکنیم و تنظیمات دیتابیس کمی تغییر میدیم:package main

import (
   &quot;os&quot;
   &quot;log&quot;

   &quot;upper.io/db.v3/mysql&quot;
   &quot;upper.io/db.v3/lib/sqlbuilder&quot;
   _ &quot;github.com/joho/godotenv/autoload&quot;
)

var settings = mysql.ConnectionURL{
   Database: os.Getenv(&quot;MYSQL_DATABASE&quot;),
   Host:     os.Getenv(&quot;MYSQL_ADDRESS&quot;),
   User:     os.Getenv(&quot;MYSQL_USERNAME&quot;),
   Password: os.Getenv(&quot;MYSQL_PASSWORD&quot;),
}

var MySql sqlbuilder.Database

func main() {

   var DBError error
   MySql, DBError = mysql.Open(settings)
   if DBError != nil {
      log.Fatal(&quot;MySQL Error: &quot;, DBError)
   }
   MySql.SetLogging(false)
   defer MySql.Close()

   router := setupRouter()

   router.Run() // listen and serve on 0.0.0.0:8080
}الآن همه چی درسته ولی فایلی برای خوندن وجود نداره، باید فایل .env به پروژه اضافه کنیم ولی چون ممکنه کانفیگ هرکس تو سیستم خودش با دیگری فرق داشته باشه ما فایل به اسم .env.testing درست می‌کنیم و فایل .env تو گیت ایگنور میذاریم تاهرکس بعد کلون کردن پروژه فایل به .env تغییر نام بده و شخصی سازی کنه، پس به این صورت متغییرها تو فایل .env.testing معرفی می‌کنیم:# MySQL Config
MYSQL_DATABASE=hesab_fun
MYSQL_ADDRESS=localhost
MYSQL_USERNAME=root
MYSQL_PASSWORD=123456
MYSQL_PORT=3306همینطور فایل .gitignore به این صورت تغییر میدیم:vendor
.idea
hesabFun
.envالآن رو سیستم MySQL نصب کنید به راحتی کانکت میشه البته یادتون نره دیتابیسی با اسم hesub_fun درست کنید، حالا باید بریم سراغ ساخت جدول‌های دیتابیس، برای این کار از مایگریشن استفاده میکنیم و هیچ‌وقت دستی دیتابیس دستکاری نمی‌کنیم.من از مایگریشن در کامندلاین استفاده میکنم، ترجیح میدم پروژه خیلی سنگین نکنم و چیزایی که نیاز نیست بهش اضافه نکنم، اگه داکیومنتش خونده باشید متوجه می‌شید برای نصبش باید دستور go get -v github.com/rubenv/sql-migrate/... اجرا کنید.حالا براش فایل کانفیگی با اسم dbconfig.yml ایجاد می‌کنیم:development:
    dialect: mysql
    datasource: ${MYSQL_USERNAME}:${MYSQL_PASSWORD}@tcp(${MYSQL_ADDRESS}:${MYSQL_PORT})/$MYSQL_DATABASE?charset=utf8&amp;parseTime=True&amp;loc=Local
    dir: ./migrations
    table: migrations

seed:
    dialect: mysql
    datasource: ${MYSQL_USERNAME}:${MYSQL_PASSWORD}@tcp(${MYSQL_ADDRESS}:${MYSQL_PORT})/$MYSQL_DATABASE?charset=utf8&amp;parseTime=True&amp;loc=Local
    dir: ./seeds
    table: seedsمیریم تو روت پروژه و فولدرهای migrations و seeds می‌سازیم، با دستور sql-migrate new create_users_table جدول کاربران ایجاد می‌کنیم.حالا اگه بریم تو فولدر migrations فایل جدول کاربران می‌بینیم، محتوای فایل به همچین چیزی تغییر میدیم:-- +migrate Up
CREATE TABLE `users` (
  `id` int(10) UNSIGNED NOT NULL,
  `created_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP,
  `updated_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP,
  `deleted_at` timestamp NULL DEFAULT null,
  `name` varchar(255) NOT NULL DEFAULT &#039;&#039;,
  `email` varchar(255) NOT NULL DEFAULT &#039;&#039;,
  `mobile` varchar(255) NOT NULL DEFAULT &#039;&#039;,
  `password` varchar(255) DEFAULT NULL,
  `status` enum(&#039;pending&#039;,&#039;active&#039;,&#039;block&#039;) NOT NULL DEFAULT &#039;pending&#039;,
  `type` enum(&#039;user&#039;,&#039;admin&#039;,&#039;god&#039;) NOT NULL DEFAULT &#039;user&#039;,
  `remember_token` varchar(255) DEFAULT NULL,
  `sms_token` int(11) DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

ALTER TABLE `users`
  ADD PRIMARY KEY (`id`),
  ADD UNIQUE KEY `email` (`email`,`mobile`);

ALTER TABLE `users`
  MODIFY `id` int(10) UNSIGNED NOT NULL AUTO_INCREMENT;
COMMIT;

-- +migrate Down
DROP TABLE `users`;حالا با دستور sql-migrate new -env=seed create_user فایل جدیدی برای ساخت یوزر تست ایجاد می‌کنیم با همچین محتوایی:-- +migrate Up
INSERT INTO `users` ( `created_at`, `updated_at`, `deleted_at`, `name`, `email`, `mobile`, `password`, `status`, `type`, `remember_token`, `sms_token`) VALUES
  (&#039;2018-02-04 18:10:59&#039;, &#039;2018-02-04 18:10:59&#039;, null, &#039;عرفان&#039;, &#039;&#039;, &#039;09111111111&#039;, &#039;25d55ad283aa400af464c76d713c07ad&#039;, &#039;active&#039;, &#039;user&#039;, &#039;&#039;, 0);

-- +migrate Down
DELETE FROM `users` WHERE `users`.`mobile` = &#039;09111111111&#039;;خب حالا میخوایم میخوایم جدول ساخته بشه و دیتای تست بهش وارد بشه، قبل هرچیزی باید متغییرهای دیتابیس به سیستم مون اضافه کنیم، تو مک به این صورت عمل میکنیم. اول فایل .bash_profile باز میکنیم:nano ~/.bash_profileبعد محتوای زیر بیش اضافه می‌کنیم:export MYSQL_DATABASE=hesab_fun
export MYSQL_ADDRESS=localhost
export MYSQL_USERNAME=root
export MYSQL_PASSWORD=
export MYSQL_PORT=3306توجه کنید اگر تنظیمات دیتابیس شما غیر اینه، حتما تو فایل بالا تغییرش بدید.حالا کافیه تو روت پروژه دستور sql-migrate up بزنیم تا جدول users ایجاد بشه، برای اضافه کردن دیتای تست دستور sql-migrate up -env=seed میزنیم و دیتا به جدول کاربران اضافه میشه.تا اینجا همه چی بی نقصه، حالا باید بریم سراغ تست اپ، فایل .gitlab-ci.yml باز میکنیم و به این صورت تغییرش میدیم:image: golang:1.10

stages:
  - build
  - test

services:
  - mysql:latest

variables:
  MYSQL_DATABASE: hesab_fun
  MYSQL_ROOT_PASSWORD: 123456
  MYSQL_ADDRESS: mysql
  MYSQL_PORT: 3306
  MYSQL_PASSWORD: 123456
  MYSQL_USERNAME: root

before_script:
  - curl https://glide.sh/get | sh
  - mkdir -p /go/src/gitlab.com/
  - cp -r /builds/erfun /go/src/gitlab.com/erfun
  - cd /go/src/gitlab.com/erfun/hesabFun
  - glide install
  - go get github.com/rubenv/sql-migrate/...
  - sql-migrate up
  - sql-migrate up -env=seed

build-project:
  stage: build
  script:
    - go build

test-project:
  stage: test
  script:
    - go test -v -coverاول از همه سرویس MySQL اضافه کردیم، بعد متغییرها دیتابیس اضافه کردیم، در آخر هم مایگریشن نصب و اجرا کردیم.اگه به نظرتون پیچیده شد، توصیه میکنیم کامیت‌ها تو گیت لب چک کنید و با دقت بخونید. بعد هر پست کدها کامیت می‌کنم.</description>
                <category>عرفان</category>
                <author>عرفان</author>
                <pubDate>Mon, 05 Mar 2018 01:06:10 +0330</pubDate>
            </item>
                    <item>
                <title>حساب‌فان (بدون تست هرگز)</title>
                <link>https://virgool.io/@erfun/%D8%AD%D8%B3%D8%A7%D8%A8%D9%81%D8%A7%D9%86-%D8%A8%D8%AF%D9%88%D9%86-%D8%AA%D8%B3%D8%AA-%D9%87%D8%B1%DA%AF%D8%B2-mh0pjqf18lui</link>
                <description>قبلا از اهمیت تست نوشتم، شاید اولش براتون سخت باشه، ولی کافیه یکبار با تست بنویسید بعد اون نمی‌تونید هیچ پروژه‌ای بدون تست بنویسید، درضمن اگر بخواید جاهای خوب استخدام بشید نیاز دارید تست نوشتن بلد باشید :)برای نوشتن تست مجبوریم یکم پروژه تغییر بدیم وگرنه تست‌ها به راحتی اجرا نمیشن. (برای همین میگم از همون اول تست بنویسید)اول از همه یک فایل جدید به اسم router.go درست می‌کنیم.package main

import &quot;github.com/gin-gonic/gin&quot;

func setupRouter() *gin.Engine {

   router := gin.Default()

   router.GET(&quot;/&quot;, func(c *gin.Context) {
      c.JSON(200, gin.H{
         &quot;message&quot;: &quot;Hello world!&quot;,
      })
   })

   return router
}همچنین میریم سراغ فایل اصلی پروژه یعنی main.go و کمی تغییرش میدیم.package main

func main() {
   router := setupRouter()

   router.Run() // listen and serve on 0.0.0.0:8080
}تغییرات واضحه، روتر جدا کردیم، نه بیشتر.قبل هرکاری پکیج تست به glide.yaml اضافه میکنیم و دستور glide update میزنیمpackage: gitlab.com/erfun/hesabFun
homepage: https://hesabfun.com

owners:
- name: ErFUN KH
  email: erfun.kh@gmail.com
  homepage: https://erfun.net

import:
- package: github.com/gin-gonic/gin
  version: ~1.2.0

# testing package
- package: github.com/stretchr/testifyحالا باید تست بنویسیم، برای شروع فایل router_test.go ایجاد کنید همراه محتوای زیر:package main

import (
   &quot;net/http&quot;
   &quot;net/http/httptest&quot;
   &quot;testing&quot;
   &quot;encoding/json&quot;

   &quot;github.com/stretchr/testify/assert&quot;
   &quot;github.com/gin-gonic/gin&quot;
)

func TestHelloWorldRoute(t *testing.T) {
   router := setupRouter()

   w := httptest.NewRecorder()
   req, _ := http.NewRequest(&quot;GET&quot;, &quot;/&quot;, nil)
   router.ServeHTTP(w, req)

   //JSON encode our body data
   jsonEncoded, _ := json.Marshal(gin.H{&quot;message&quot;:&quot;Hello world!&quot;})

   assert.Equal(t, 200, w.Code)
   assert.Equal(t, string(jsonEncoded), w.Body.String())
}یه فانکشن برای تست helloWorld نوشتیم، یه شئی از روترمون ایجاد کردیم، ریکوئست ارسال کردیم، یه شئی دیگه از جوابی که درصورت درست بودن همه چی باید بگیریم درست کردیم، دو خط آخر هم استاتوس کد و جوابی که گرفتیم چک کردیم تا درست باشه، درصورت هرگونه مقایرت تست خطا میده و ما متوجه میشیم کد به درستی کار نمیکنه، البته این خیلی ساده بود، در صورتی که کد شما ممکنه خطا صادر کنه شما باید اون خطاها چک کنید تا مطمئن بشید درصورت حالت خاص کد به درستی خطا پاس میده. حالا بعدا عملی می‌نویسیم خیلی بهش فکر نکنید.حالا که تست داریم باید بعد هرکامیت تست به صورت خودکار داخل گیت‌لب اجرا بشه و به ما خبر بده، برای این کار فایل .gitlab-ci.yml تو روت پروژه ایجاد میکنیم و داخلش اینطور پر میکنیم.image: golang:1.10

stages:
  - build
  - test

before_script:
  - curl https://glide.sh/get | sh
  - mkdir -p /go/src/gitlab.com/
  - cp -r /builds/erfun /go/src/gitlab.com/erfun
  - cd /go/src/gitlab.com/erfun/hesabFun
  - glide install

build-project:
  stage: build
  script:
    - go build

test-project:
  stage: test
  script:
    - go test -v -coverخط اول گفتیم از ایمیج رسمی گولنگ ورژن ۱.۱۰ استفاده میکنیم، یعنی سروری که برامون ران میکنه روش گولنگ نصبه (اگه درک نمی‌کنید چی میگم باید درمورد داکر بخونید، آموزش فارسی زیادی براش هست کافیه گوگل کنید)خط بعدی گفتیم دوتا استیج بیلد و تست داریمتو قسمت بی‌فور اسکریپت میگیم اول گلاید نصب کنه، بعد فایل پروژه کپی کنه جای استانداردش و پکیج‌هارو با گلاید دانلود کنه.حالا تو استیج بیلد سعی میکنه پروژه بیلد کنه، اگه با موفقیت انجام بشه میره برای استیج تست، اگه تست با موفقیت انجام بشه یعنی کدهامون درست کار میکنه.البته انجام تمام این مراحل با کل لاگ‌ها می‌تونید از قسمت پایپ‌لاین پروژه داخل سایت گیت لب ببینید.یه همچین چیزی، ولی خب فعلا شلوغه و رو پندینگ مونده، تستا که انجام بشه دوتا تیک سبز می‌خوره.بعد اجرای صحیح تست‌ها با همچین چیزی مواجه می‌شید.حالا اگه تست‌ها پاس بشه اتفاق خاصی نمیفته ولی اگه خطایی بده براتون ایمیل ارسال میشه که فلان خطا داد، ولی ایمیل کمی کنده، ترجیح من اینه نوتیفیکیشن بده، برای این کار ربات تلگرام هست ولی اگه اسلک پیشنهاد میدید از تنظیمات پروژه در سایت گیت لب در دسترس تونه.</description>
                <category>عرفان</category>
                <author>عرفان</author>
                <pubDate>Sat, 03 Mar 2018 23:02:20 +0330</pubDate>
            </item>
            </channel>
</rss>