<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: mrthkc</title>
    <description>The latest articles on DEV Community by mrthkc (@mrthkc).</description>
    <link>https://dev.to/mrthkc</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F504223%2F4dd6344d-bd96-4c22-8fcd-2295722f79d2.jpg</url>
      <title>DEV Community: mrthkc</title>
      <link>https://dev.to/mrthkc</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/mrthkc"/>
    <language>en</language>
    <item>
      <title>GO Backend Service Skeleton w/Gin</title>
      <dc:creator>mrthkc</dc:creator>
      <pubDate>Sat, 09 Jan 2021 12:36:27 +0000</pubDate>
      <link>https://dev.to/mrthkc/golang-backend-service-skeleton-w-gin-2h2n</link>
      <guid>https://dev.to/mrthkc/golang-backend-service-skeleton-w-gin-2h2n</guid>
      <description>&lt;h2&gt;
  
  
  Intro
&lt;/h2&gt;

&lt;p&gt;Starting this post I assume that you have basic installation of GoLang on your local environment. If not you can follow steps from; &lt;a href="https://golang.org/doc/install" rel="noopener noreferrer"&gt;Download and Install - The Go Programming Language&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Go Directory
&lt;/h2&gt;

&lt;p&gt;After download and install steps, your go(path) directory should be like below;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fswh90814pze8sxtqekw8.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fswh90814pze8sxtqekw8.png" alt="Go Path"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You need to create your awesome project's folder under &lt;code&gt;src/&lt;/code&gt; directory. If you are using GitHub to host your codes, it would be better to start project under path like;&lt;br&gt;
&lt;code&gt;~/go/src/github.com/{github-username}/{project-name}&lt;/code&gt;. That makes easier using go commands such as &lt;code&gt;go get&lt;/code&gt;, &lt;code&gt;go install&lt;/code&gt; etc.&lt;/p&gt;
&lt;h3&gt;
  
  
  Go Project Directory View
&lt;/h3&gt;

&lt;p&gt;Now We are ready to open our preferred Go IDE to form project directory layout and code. Here you are my layout;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fu2dics24t6xysbzjwnwt.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fu2dics24t6xysbzjwnwt.png" alt="Layout"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I will try to explain every file in this directory layout from top to bottom and you can start your backend project by making changes specific to you.&lt;/p&gt;
&lt;h4&gt;
  
  
  Cmd -/api
&lt;/h4&gt;

&lt;p&gt;Under &lt;code&gt;cmd&lt;/code&gt; we can keep starting the system scripts. In this example you can see &lt;code&gt;api/main.go&lt;/code&gt; to start our local backend service. Also, you can store fetchers, data importers or fixing scripts under this directory. To run;&lt;br&gt;
&lt;code&gt;go run cmd/api/main.go&lt;/code&gt;, specifically for this sample.&lt;/p&gt;

&lt;p&gt;Let's see main.go;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;package main

import (
    "flag"

    "github.com/{github-username}/{project}/internal/api"
    "github.com/{github-username}/{project}/internal/pkg/conf"
    log "github.com/sirupsen/logrus"
)

func main() {
    env := flag.String("env", "local", "environment")
    flag.Parse()

    config, err := conf.NewConfig("config/default.yml", *env)
    if err != nil {
        log.Fatalf("Can not reead config: %v", err)
    }

    config.Env = *env
    config.DBCred = config.Mysql.Local
    config.Secret = config.JWT.Local.Secret
    if config.Env == "prod" {
        config.DBCred = config.Mysql.Prod
        config.Secret = config.JWT.Prod.Secret
    }

    log.Info("SDK API started w/Env: " + *env)
    api.Listen(config)
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As you can see basically we are reading service's configuration and starting the API by using &lt;code&gt;Listen&lt;/code&gt; function under api package.&lt;/p&gt;

&lt;h4&gt;
  
  
  Config -/default.yml
&lt;/h4&gt;

&lt;p&gt;As you can guess, under this directory we are storing our configurations and credentials. For secrets we can think another location but basically it is OK to keep them here.&lt;/p&gt;

&lt;p&gt;Also yml file is a personal decision, you can select your favorite format such as json, xml, etc.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;mysql:
  local:
    host: localhost
    port: 3306
    db: project
    user: root
    password: 12345678
  prod:
    host: prod_db
    port: 3306
    db: project
    user: user
    password: strong_pass

jwt:
  local:
    secret: "test"
  prod:
    secret: "prod"

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Internal
&lt;/h4&gt;

&lt;p&gt;Under internal directory we store our service's base scripts. I divided the code base mainly two parts;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;service (api) - it could be fetcher, importer, etc.&lt;/li&gt;
&lt;li&gt;pkg - structs and functions that we will use for services.&lt;/li&gt;
&lt;/ol&gt;

&lt;h5&gt;
  
  
  Api (Service)
&lt;/h5&gt;

&lt;p&gt;As I mentioned main functions for an API backend service locating here. Yes, main listen function, cors header function and router for this example;&lt;/p&gt;

&lt;p&gt;base.go: by using configuration starting gin&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// Listen : starts api by listening to incoming requests
func Listen(c *conf.Config) {
    service.Config = c

    if service.Config.Env == "prod" {
        gin.SetMode(gin.ReleaseMode)
    }
    r := gin.New()
    r.Use(CORS())

    // router.go
    route(r)

    server := &amp;amp;http.Server{
        Addr:         ":9090",
        Handler:      r,
        ReadTimeout:  60 * time.Second,
        WriteTimeout: 60 * time.Second,
    }

    err := server.ListenAndServe()
    if err != nil &amp;amp;&amp;amp; err != http.ErrServerClosed {
        log.Fatalf("Server can not start: %v", err)
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;CORS function that we use to set our response headers.&lt;br&gt;
You can change them specifically by looking how you request and collect responses from backend service.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// CORS : basic cors settings
func CORS() gin.HandlerFunc {
    return func(c *gin.Context) {
        c.Writer.Header().Set("Access-Control-Allow-Origin", "*")
        c.Writer.Header().Set("Access-Control-Allow-Credentials", "true")
        c.Writer.Header().Set("Access-Control-Allow-Headers", "Content-Type, Content-Length, Accept-Encoding, X-CSRF-Token, Authorization, accept, origin, Cache-Control, X-Requested-With")
        c.Writer.Header().Set("Access-Control-Allow-Methods", "POST, OPTIONS, GET, PUT, DELETE")

        if c.Request.Method == "OPTIONS" {
            c.AbortWithStatus(204)
            return
        }
        c.Next()
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;router.go: Classical router.&lt;br&gt;
Important parts:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;endpoint to function match&lt;/li&gt;
&lt;li&gt;using middleware(JWTAuth()) and grouping endpoints.
&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;func route(r *gin.Engine) {
    // Base
    r.GET("/api/", service.BaseHandler)

    // Register
    r.POST("/api/user", service.Register)
    r.POST("/api/login", service.Login)

    authorized := r.Group("/api/")
    authorized.Use(service.JWTAuth())
    {
        authorized.GET("token", service.Token)
        authorized.GET("user/:uid/profile", service.Profile)
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h5&gt;
  
  
  Pkg
&lt;/h5&gt;

&lt;p&gt;We store our service's deep and detailed parts in here.&lt;/p&gt;
&lt;h6&gt;
  
  
  Adapters:
&lt;/h6&gt;

&lt;p&gt;In my project, I am using MySQL DB only but by looking your project's requirements you can multiple adapters like a NoSQL which needs to establish a connection.&lt;/p&gt;

&lt;p&gt;mysql.go:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;func NewClient(host string, port string, dbName string, uName string, pass string) (db *sql.DB) {
    db, err := sql.Open("mysql", uName+":"+pass+"@tcp("+host+":"+port+")/"+dbName)
    if err != nil {
        panic(err.Error())
    }
    return db
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h6&gt;
  
  
  Configuration Reader:
&lt;/h6&gt;

&lt;p&gt;To read our configuration file into a struct:&lt;br&gt;
conf/reader.go:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;type Config struct {
    Env   string
    Mysql struct {
        Local struct {
            Host     string `yaml:"host"`
            Port     string `yaml:"port"`
            DB       string `yaml:"db"`
            User     string `yaml:"user"`
            Password string `yaml:"password"`
        } `yaml:"local"`
        Prod struct {
            Host     string `yaml:"host"`
            Port     string `yaml:"port"`
            DB       string `yaml:"db"`
            User     string `yaml:"user"`
            Password string `yaml:"password"`
        } `yaml:"prod"`
    } `yaml:"mysql"`
    JWT struct {
        Local struct {
            Secret     string `yaml:"secret"`
        } `yaml:"local"`
        Prod struct {
            Secret     string `yaml:"secret"`
        } `yaml:"prod"`
    } `yaml:"jwt"`

    DBCred struct {
        Host     string `yaml:"host"`
        Port     string `yaml:"port"`
        DB       string `yaml:"db"`
        User     string `yaml:"user"`
        Password string `yaml:"password"`
    }
    Secret string
}

// NewConfig returns a new decoded Config struct
func NewConfig(configPath string, env string) (*Config, error) {
    // Create config structure
    config := &amp;amp;Config{}

    // Open config file
    file, err := ioutil.ReadFile(configPath)
    if err != nil {
        return nil, err
    }

    // Init new YAML decode
    err = yaml.Unmarshal(file, config)
    if err != nil {
        return nil, err
    }

    return config, nil
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h6&gt;
  
  
  Entity:
&lt;/h6&gt;

&lt;p&gt;Under entity directory, first of all under &lt;code&gt;entity/main.go&lt;/code&gt; I preferred to keep a DB variable for usage of all possible entities.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// DB : DB for all entities
var DB *sql.DB
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And as you can guess, in &lt;code&gt;entity/user.go&lt;/code&gt; we are using this DB variable for &lt;code&gt;SELECT/INSERT/UPDATE&lt;/code&gt; querying functions. Also a map struct for DB user schema to make easier to read rows.&lt;/p&gt;

&lt;p&gt;user.go:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// User : user entity
type User struct {
    ID          int        `json:"id"`
    Email       string     `json:"email"`
    Password    string     `json:"password"`
    Fullname    string     `json:"fullname"`
    TSLastLogin int32      `json:"ts_last_login"`
    TSCreate    int32      `json:"ts_create"`
    TSUpdate    int32      `json:"ts_update"`
    Permission  json.RawMessage `json:"permission"`
}

// GetUserByEmail : returns single user
func GetUserByEmail(email string) *User {
    user := new(User)
    row := DB.QueryRow("SELECT * from user WHERE email=?", email)
    err := row.Scan(&amp;amp;user.ID, &amp;amp;user.Email, &amp;amp;user.Password, &amp;amp;user.Fullname, &amp;amp;user.TSLastLogin, &amp;amp;user.TSCreate, &amp;amp;user.TSUpdate, &amp;amp;user.Permission)
    if err != nil {
        log.Errorln("User SELECT by Email Err: ", err)
        return nil
    }
    return user
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h6&gt;
  
  
  Service:
&lt;/h6&gt;

&lt;p&gt;Under service directory there are handlers / middleware functions. These functions are the base point to respond requests coming from any external service.&lt;/p&gt;

&lt;p&gt;Under &lt;code&gt;service/base.go&lt;/code&gt;, we can add a baseHandler to check health of the service.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// BaseHandler : home - health-test
func BaseHandler(c *gin.Context) {
    log.Info("Base")
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Under &lt;code&gt;service/jwt.go&lt;/code&gt;, you can find the middleware function to validate and auth token, that we called from router.go as it is mentioned.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// ValidateToken :
func ValidateToken(encodedToken string) (*jwt.Token, error) {
    return jwt.Parse(encodedToken, func(token *jwt.Token) (interface{}, error) {
        if _, isvalid := token.Method.(*jwt.SigningMethodHMAC); !isvalid {
            return nil, errors.New("invalid token")
        }
        return []byte(Config.Secret), nil
    })
}

// JWTAuth :
func JWTAuth() gin.HandlerFunc {
    return func(c *gin.Context) {
        var bearer = "Bearer"
        authHeader := c.GetHeader("Authorization")
        if authHeader == "" {
            c.AbortWithStatus(http.StatusUnauthorized)
            return
        }

        tokenString := authHeader[len(bearer):]
        token, err := ValidateToken(tokenString)
        if token.Valid {
            claims := token.Claims.(jwt.MapClaims)
            log.Debug(claims)
        } else {
            log.Error(err.Error())
            c.AbortWithStatus(http.StatusUnauthorized)
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And lastly, &lt;code&gt;service/user.go&lt;/code&gt;, there are functions related to user schema. Such as login, register, profile, etc. Here you are login function that we used in router.go to respond &lt;code&gt;api/login&lt;/code&gt; endpoint.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// Login :
func Login(c *gin.Context) {
    entity.DB = adapter.NewClient(Config.DBCred.Host, Config.DBCred.Port, Config.DBCred.DB, Config.DBCred.User, Config.DBCred.Password)
    defer entity.DB.Close()

    user := new(entity.User)
    if err := c.BindJSON(user); err != nil {
        log.Error("Binding user error occured: ", err)
        c.JSON(http.StatusBadRequest, "Binding error")
        return
    }

    checkUser := entity.GetUserByEmail(user.Email)
    if checkUser == nil {
        c.JSON(http.StatusBadRequest, gin.H{
            "success": 0,
        })
        return
    }

    login := comparePasswords(checkUser.Password, []byte(user.Password))
    if login == false {
        c.JSON(http.StatusForbidden, gin.H{
            "success": 0,
        })
        return
    }

    token, err := GenerateToken(checkUser.ID)
    if err != nil {
        c.JSON(http.StatusForbidden, gin.H{
            "success": 0,
        })
        return
    }
    c.JSON(http.StatusOK, gin.H{
        "success": 1,
        "data":    gin.H{
            "uid": checkUser.ID,
            "uemail": checkUser.Email,
            "token": token,
        },
    })
    return
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As a conclusion, I am using this layout while starting any side project's backend service. By adding new entities, service functions and endpoints, it is really sufficient to save your time that is spent in starting step.&lt;/p&gt;

&lt;p&gt;Cheers.&lt;/p&gt;

</description>
      <category>go</category>
      <category>beginners</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Mapping with AWS S3 Select — Cost Efficient Solution</title>
      <dc:creator>mrthkc</dc:creator>
      <pubDate>Sat, 07 Nov 2020 17:53:44 +0000</pubDate>
      <link>https://dev.to/mrthkc/mapping-with-aws-s3-select-cost-efficient-solution-1j3e</link>
      <guid>https://dev.to/mrthkc/mapping-with-aws-s3-select-cost-efficient-solution-1j3e</guid>
      <description>&lt;p&gt;Welcome to my first 'Dev' post. I want you to know this is not my first post. I'm moving my post from medium because I know here is better platform for me to follow everything about my profession. Good reading.&lt;/p&gt;




&lt;p&gt;From April 10th;&lt;/p&gt;

&lt;p&gt;We, developers, love both creating and using high performing ( eg: fast responding) functions, softwares and applications. Generally, one of our finalization steps is performing speed and load tests on our products, especially if we are using a third party service with the related parts.&lt;/p&gt;

&lt;p&gt;Nowadays fast and flexible NoSql technologies are very popular. There are many tools which serve the response instantly and consistently. Let’s look at AWS’ DynamoDB as an example. However, we have one main problem with DynamoDB. Write requests like INSERT / UPDATE and their reserved capacity are highly priced. If you want fast responses, you need to insert these data to DynamoDB first which could be really costly under high traffic load.&lt;/p&gt;

&lt;p&gt;My ex work place was a platform that links advertisers who could be app owners and media companies with ad publishers. If you are working on mobile advertising data, you need to have some solutions to detect fraudulent traffic. While redirecting advertisement clicks to APP stores, we must make sure that possible install and in-app-events would come from real users.&lt;/p&gt;

&lt;p&gt;As you can guess, we are operating on a lot of click, install and in-app-event data by matching them with each other and detecting untrustworthy ones.&lt;/p&gt;




&lt;p&gt;Now let’s start with the logic of how we are matching all these data, under high traffic load despite the fraudulent traffic. First, we need to create a mechanism to generate smart identifiers for your objects which should be shared with external platforms and businesses. Smarts IDs should be encrypted, with a strong key, to not give any room to AD fakers but also contain some information about advertisement items which are progressing.&lt;/p&gt;

&lt;p&gt;Let’s get back to AWS. It’s time to talk about how we were using some of their services. Remember smart identifiers, besides them, we need to keep some data like click date-time (to detect the passed time between an install or in-app-event and click). Relational databases could be very compulsive if you are not loaded and do not have many servers (Plus we need to think about the maintenance costs).&lt;/p&gt;

&lt;p&gt;So, we needed a backup storage first, which we decided to go with S3(Simple Storage Service). You can use both JSON and CSV / TSV format to store  my ex work place's traffic data. (eg: clicks, in-app-events etc.)&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{id:&amp;lt;strong-generated-id&amp;gt;, ts: 987026400, …}
{id:&amp;lt;strong-generated-id&amp;gt;, ts: 1069974000, …}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We planned S3 stored data for only backup and side usages, we also had one design with NoSql to store and map our data, which is DynamoDB. We were inserting tons of click data to DynamoDB after buffering to match these clicks with installs and events as fast as we can.&lt;/p&gt;

&lt;p&gt;We do not have to wait too long for this design to be a costly one. Let’s go over the February 6th DynamoDB writing cost as an example.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;February 6th Write Capacity Unit(WCU):&lt;/strong&gt; 138,798&lt;br&gt;
&lt;strong&gt;February 6th Cost:&lt;/strong&gt; $101.06&lt;/p&gt;

&lt;p&gt;By looking at this you can see that one of our biggest expense is AWS DynamoDB write requests. (Think about some days WCU is raising to 175K)&lt;/p&gt;



&lt;p&gt;At last, after all of the above analysis I am ready to explain our solution for the cost reduction.I told you about our smart IDs and storing them in S3. We noticed that with minor changes we could understand which click item was stored in which S3 file. Take a look at this;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Our smart IDs;&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Encrypt (&amp;lt;execution_ts&amp;gt;+&amp;lt;related_ad_campaign&amp;gt;+&amp;lt;random_alpha_numeric&amp;gt;+&amp;lt;server_identifier&amp;gt;);
Encrypt (987026407+123321+ABcd1234EF56+55533);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;So, S3 keys are like&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;S3_key: &amp;lt;item_type&amp;gt;+&amp;lt;buffered_ts&amp;gt;+&amp;lt;server_identifier&amp;gt;+&amp;lt;random_alpha_numeric&amp;gt;
S3_key: click+987026400+55533+ZZXX112233VVPP
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, it’s easy to determine which traffic item is in which S3 file when an install or in-app-event comes with a converted click’s smart ID. It’s not done yet. In buffered S3 files there are many other click data and we need to find the correct one from the conversion ID. Because we stored the click details in JSON or CSV/TSV format we can use the S3-Select function &lt;a href="https://aws.amazon.com/about-aws/whats-new/2018/04/amazon-s3-select-is-now-generally-available/" rel="noopener noreferrer"&gt;https://aws.amazon.com/about-aws/whats-new/2018/04/amazon-s3-select-is-now-generally-available/&lt;/a&gt;, like;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;func S3Select(bucket, key, itemID){ …use your AWS library’s S3 select… }
item = S3Select(‘traffic’, ‘click+987026400+55533+ZZXX112233VVPP’, &amp;lt;encrypted-smart-identifier&amp;gt;)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;p&gt;Last of all, by organizing your item identifiers and S3 file names in a logic, you can use Simple Storage Service, ‘select’ function to attribute your relational items cost effectively. Before finishing my first medium post, I want to share DynamoDB cost graphs which includes the exact date that we updated our attribution logic;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fcvx486pxkxjv7afatlxa.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fcvx486pxkxjv7afatlxa.png" alt="GR-1"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Monthly perspective;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2F3fotolefsfv0oyjn16ea.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2F3fotolefsfv0oyjn16ea.png" alt="GR-2"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Line chart below is showing S3 API Requests’ costs for February;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fur9r0ymvk8uk4j18nnhg.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fur9r0ymvk8uk4j18nnhg.png" alt="GR-3"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;As you can see there are no remarkable changes after mapping logic change.&lt;/p&gt;

&lt;p&gt;If fast responses are not essential, if you can handle your pairing objects processes asynchronously and if you are not extremely wealthy, then you can prefer S3 instead of DynamoDB for both storing and retrieving your data.&lt;/p&gt;

</description>
      <category>aws</category>
      <category>cloud</category>
      <category>webdev</category>
      <category>nosql</category>
    </item>
    <item>
      <title>Hello dev.to</title>
      <dc:creator>mrthkc</dc:creator>
      <pubDate>Mon, 02 Nov 2020 10:47:19 +0000</pubDate>
      <link>https://dev.to/mrthkc/hello-dev-to-c5c</link>
      <guid>https://dev.to/mrthkc/hello-dev-to-c5c</guid>
      <description>&lt;p&gt;Hello dev.to,&lt;/p&gt;

&lt;p&gt;My name is Mert and I live in Ankara, 10 year experienced software developer. So far I have worked for five startup companies as a backend developer. I mostly used php, python3, GoLang and JS.&lt;/p&gt;

&lt;p&gt;I just opened my account and my objective is stay in touch and follow related software development posts.&lt;/p&gt;

&lt;p&gt;My interests;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;python&lt;/li&gt;
&lt;li&gt;Django w/python3&lt;/li&gt;
&lt;li&gt;GoLang&lt;/li&gt;
&lt;li&gt;React&lt;/li&gt;
&lt;li&gt;Flutter&lt;/li&gt;
&lt;li&gt;Cloud solutions (AWS, Oracle, Google Cloud etc.)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Also I'll try to share posts that include interesting development issues and solutions which I experience.&lt;/p&gt;

&lt;p&gt;Happy to be here.&lt;/p&gt;

</description>
      <category>intro</category>
      <category>hello</category>
      <category>developer</category>
      <category>software</category>
    </item>
  </channel>
</rss>
