One place for hosting & domains

      Defining

      Defining Your App Specification on DigitalOcean App Platform


      Best practices for defining your app specification on App Platform, DigitalOcean’s take on a modern PaaS that makes it simpler to build, deploy, and scale apps.

      How to Join

      This Tech Talk is free and open to everyone. Register on Eventbrite here to receive a link to join on Tuesday, December 1, 2020, 12:00–1:00 p.m. ET.

      About the Talk

      It’s every developer’s dream to simply write code, click a button, and then automatically deploy and run their code at scale for millions or even billions of users. Join this Tech Talk to learn how DigitalOcean App Platform can help you reduce both complexity and costs.

      Explore the underlying specification that defines your app on App Platform to gain more control and understanding. This talk will guide you through all of the options available to you and teach you best practices for defining your app specification.

      What You’ll Learn

      • How your application is defined under the hood
      • Best practices for writing and defining your App Spec, a YAML manifest that declaratively states everything App Platform knows about your app.

      This Talk is Designed For

      Resources

      About the Presenter

      Nicholas Tate is a senior engineer on the App Platform team at DigitalOcean. He has worked on the DigitalOcean Managed Kubernetes team and before that, on multi-cloud Kubernetes at Containership. He loves everything and anything related to cloud native infrastructure and enjoys playing guitar and rock climbing.

      To join the live Tech Talk, register here.



      Source link

      Defining Methods in Go


      Introduction

      Functions allow you to organize logic into repeatable procedures that can use different arguments each time they run. In the course of defining functions, you’ll often find that multiple functions might operate on the same piece of data each time. Go recognizes this pattern and allows you to define special functions, called methods, whose purpose is to operate on instances of some specific type, called a receiver. Adding methods to types allows you to communicate not only what the data is, but also how that data should be used.

      Defining a Method

      The syntax for defining a method is similar to the syntax for defining a function. The only difference is the addition of an extra parameter after the func keyword for specifying the receiver of the method. The receiver is a declaration of the type that you wish to define the method on. The following example defines a method on a struct type:

      package main
      
      import "fmt"
      
      type Creature struct {
          Name     string
          Greeting string
      }
      
      func (c Creature) Greet() {
          fmt.Printf("%s says %s", c.Name, c.Greeting)
      }
      
      func main() {
          sammy := Creature{
              Name:     "Sammy",
              Greeting: "Hello!",
          }
          Creature.Greet(sammy)
      }
      

      If you run this code, the output will be:

      Output

      Sammy says Hello!

      We created a struct called Creature with string fields for a Name and a Greeting. This Creature has a single method defined, Greet. Within the receiver declaration, we assigned the instance of Creature to the variable c so that we could refer to the fields of the Creature as we assemble the greeting message in fmt.Printf.

      In other languages, the receiver of method invocations is typically referred to by a keyword (e.g. this or self). Go considers the receiver to be a variable like any other, so you’re free to name it whatever you like. The style preferred by the community for this parameter is a lower-case version of the first character of the receiver type. In this example, we used c because the receiver type was Creature.

      Within the body of main, we created an instance of Creature and specified values for its Name and Greeting fields. We invoked the Greet method here by joining the name of the type and the name of the method with a . and supplying the instance of Creature as the first argument.

      Go provides another, more convenient, way of calling methods on instances of a struct as shown in this example:

      package main
      
      import "fmt"
      
      type Creature struct {
          Name     string
          Greeting string
      }
      
      func (c Creature) Greet() {
          fmt.Printf("%s says %s", c.Name, c.Greeting)
      }
      
      func main() {
          sammy := Creature{
              Name:     "Sammy",
              Greeting: "Hello!",
          }
          sammy.Greet()
      }
      

      If you run this, the output will be the same as the previous example:

      Output

      Sammy says Hello!

      This example is identical to the previous one, but this time we have used dot notation to invoke the Greet method using the Creature stored in the sammy variable as the receiver. This is a shorthand notation for the function invocation in the first example. The standard library and the Go community prefers this style so much that you will rarely see the function invocation style shown earlier.

      The next example shows one reason why dot notation is more prevalent:

      package main
      
      import "fmt"
      
      type Creature struct {
          Name     string
          Greeting string
      }
      
      func (c Creature) Greet() Creature {
          fmt.Printf("%s says %s!n", c.Name, c.Greeting)
          return c
      }
      
      func (c Creature) SayGoodbye(name string) {
          fmt.Println("Farewell", name, "!")
      }
      
      func main() {
          sammy := Creature{
              Name:     "Sammy",
              Greeting: "Hello!",
          }
          sammy.Greet().SayGoodbye("gophers")
      
          Creature.SayGoodbye(Creature.Greet(sammy), "gophers")
      }
      

      If you run this code, the output looks like this:

      Output

      Sammy says Hello!! Farewell gophers ! Sammy says Hello!! Farewell gophers !

      We’ve modified the earlier examples to introduce another method called SayGoodbye and also changed Greet to return a Creature so that we can invoke further methods on that instance. In the body of main we call the methods Greet and SayGoodbye on the sammy variable first using dot notation and then using the functional invocation style.

      Both styles output the same results, but the example using dot notation is far more readable. The chain of dots also tells us the sequence in which methods will be invoked, where the functional style inverts this sequence. The addition of a parameter to the SayGoodbye call further obscures the order of method calls. The clarity of dot notation is the reason that it is the preferred style for invoking methods in Go, both in the standard library and among the third-party packages you will find throughout the Go ecosystem.

      Defining methods on types, as opposed to defining functions that operate on some value, have other special significance to the Go programming language. Methods are the core concept behind interfaces.

      Interfaces

      When you define a method on any type in Go, that method is added to the type’s method set. The method set is the collection of functions associated with that type as methods and used by the Go compiler to determine whether some type can be assigned to a variable with an interface type. An interface type is a specification of methods used by the compiler to guarantee that a type provides implementations for those methods. Any type that has methods with the same name, same parameters, and same return values as those found in an interface’s definition are said to implement that interface and are allowed to be assigned to variables with that interface’s type. The following is the definition of the fmt.Stringer interface from the standard library:

      type Stringer interface {
        String() string
      }
      

      For a type to implement the fmt.Stringer interface, it needs to provide a String() method that returns a string. Implementing this interface will allow your type to be printed exactly as you wish (sometimes called “pretty-printed”) when you pass instances of your type to functions defined in the fmt package. The following example defines a type that implements this interface:

      package main
      
      import (
          "fmt"
          "strings"
      )
      
      type Ocean struct {
          Creatures []string
      }
      
      func (o Ocean) String() string {
          return strings.Join(o.Creatures, ", ")
      }
      
      func log(header string, s fmt.Stringer) {
          fmt.Println(header, ":", s)
      }
      
      func main() {
          o := Ocean{
              Creatures: []string{
                  "sea urchin",
                  "lobster",
                  "shark",
              },
          }
          log("ocean contains", o)
      }
      

      When you run the code, you’ll see this output:

      Output

      ocean contains : sea urchin, lobster, shark

      This example defines a new struct type called Ocean. Ocean is said to implement the fmt.Stringer interface because Ocean defines a method called String, which takes no parameters and returns a string. In main, we defined a new Ocean and passed it to a log function, which takes a string to print out first, followed by anything that implements fmt.Stringer. The Go compiler allows us to pass o here because Ocean implements all of the methods requested by fmt.Stringer. Within log, we use fmt.Println, which calls the String method of Ocean when it encounters a fmt.Stringer as one of its parameters.

      If Ocean did not provide a String() method, Go would produce a compilation error, because the log method requests a fmt.Stringer as its argument. The error looks like this:

      Output

      src/e4/main.go:24:6: cannot use o (type Ocean) as type fmt.Stringer in argument to log: Ocean does not implement fmt.Stringer (missing String method)

      Go will also make sure that the String() method provided exactly matches the one requested by the fmt.Stringer interface. If it does not, it will produce an error that looks like this:

      Output

      src/e4/main.go:26:6: cannot use o (type Ocean) as type fmt.Stringer in argument to log: Ocean does not implement fmt.Stringer (wrong type for String method) have String() want String() string

      In the examples so far, we have defined methods on the value receiver. That is, if we use the functional invocation of methods, the first parameter, referring to the type the method was defined on, will be a value of that type, rather than a pointer. Consequently, any modifications we make to the instance provided to the method will be discarded when the method completes execution, because the value received is a copy of the data. It’s also possible to define methods on the pointer receiver to a type.

      Pointer Receivers

      The syntax for defining methods on the pointer receiver is nearly identical to defining methods on the value receiver. The difference is prefixing the name of the type in the receiver declaration with an asterisk (*). The following example defines a method on the pointer receiver to a type:

      package main
      
      import "fmt"
      
      type Boat struct {
          Name string
      
          occupants []string
      }
      
      func (b *Boat) AddOccupant(name string) *Boat {
          b.occupants = append(b.occupants, name)
          return b
      }
      
      func (b Boat) Manifest() {
          fmt.Println("The", b.Name, "has the following occupants:")
          for _, n := range b.occupants {
              fmt.Println("t", n)
          }
      }
      
      func main() {
          b := &Boat{
              Name: "S.S. DigitalOcean",
          }
      
          b.AddOccupant("Sammy the Shark")
          b.AddOccupant("Larry the Lobster")
      
          b.Manifest()
      }
      

      You’ll see the following output when you run this example:

      Output

      The S.S. DigitalOcean has the following occupants: Sammy the Shark Larry the Lobster

      This example defined a Boat type with a Name and occupants. We want to force code in other packages to only add occupants with the AddOccupant method, so we’ve made the occupants field unexported by lowercasing the first letter of the field name. We also want to make sure that calling AddOccupant will cause the instance of Boat to be modified, which is why we defined AddOccupant on the pointer receiver. Pointers act as a reference to a specific instance of a type rather than a copy of that type. Knowing that AddOccupant will be invoked using a pointer to Boat guarantees that any modifications will persist.

      Within main, we define a new variable, b, which will hold a pointer to a Boat (*Boat). We invoke the AddOccupant method twice on this instance to add two passengers. The Manifest method is defined on the Boat value, because in its definition, the receiver is specified as (b Boat). In main, we are still able to call Manifest because Go is able to automatically dereference the pointer to obtain the Boat value. b.Manifest() here is equivalent to (*b).Manifest().

      Whether a method is defined on a pointer receiver or on a value receiver has important implications when trying to assign values to variables that are interface types.

      Pointer Receivers and Interfaces

      When you assign a value to a variable with an interface type, the Go compiler will examine the method set of the type being assigned to ensure that it has the methods the interface expects. The method sets for the pointer receiver and the value receiver are different because methods that receive a pointer can modify their receiver where those that receive a value cannot.

      The following example demonstrates defining two methods: one on a type’s pointer receiver and on its value receiver. However, only the pointer receiver will be able to satisfy the interface also defined in this example:

      package main
      
      import "fmt"
      
      type Submersible interface {
          Dive()
      }
      
      type Shark struct {
          Name string
      
          isUnderwater bool
      }
      
      func (s Shark) String() string {
          if s.isUnderwater {
              return fmt.Sprintf("%s is underwater", s.Name)
          }
          return fmt.Sprintf("%s is on the surface", s.Name)
      }
      
      func (s *Shark) Dive() {
          s.isUnderwater = true
      }
      
      func submerge(s Submersible) {
          s.Dive()
      }
      
      func main() {
          s := &Shark{
              Name: "Sammy",
          }
      
          fmt.Println(s)
      
          submerge(s)
      
          fmt.Println(s)
      }
      

      When you run the code, you’ll see this output:

      Output

      Sammy is on the surface Sammy is underwater

      This example defined an interface called Submersible that expects types having a Dive() method. We then defined a Shark type with a Name field and an isUnderwater method to keep track of the state of the Shark. We defined a Dive() method on the pointer receiver to Shark which modified isUnderwater to true. We also defined the String() method of the value receiver so that it could cleanly print the state of the Shark using fmt.Println by using the fmt.Stringer interface accepted by fmt.Println that we looked at earlier. We also used a function submerge that takes a Submersible parameter.

      Using the Submersible interface rather than a *Shark allows the submerge function to depend only on the behavior provided by a type. This makes the submerge function more reusable because you wouldn’t have to write new submerge functions for a Submarine, a Whale, or any other future aquatic inhabitants we haven’t thought of yet. As long as they define a Dive() method, they can be used with the submerge function.

      Within main we defined a variable s that is a pointer to a Shark and immediately printed s with fmt.Println. This shows the first part of the output, Sammy is on the surface. We passed s to submerge and then called fmt.Println again with s as its argument to see the second part of the output printed, Sammy is underwater.

      If we changed s to be a Shark rather than a *Shark, the Go compiler would produce the error:

      Output

      cannot use s (type Shark) as type Submersible in argument to submerge: Shark does not implement Submersible (Dive method has pointer receiver)

      The Go compiler helpfully tells us that Shark does have a Dive method, it’s just defined on the pointer receiver. When you see this message in your own code, the fix is to pass a pointer to the interface type by using the & operator before the variable where the value type is assigned.

      Conclusion

      Declaring methods in Go is ultimately no different than defining functions that receive different types of variables. The same rules of working with pointers apply. Go provides some conveniences for this extremely common function definition and collects these into sets of methods that can be reasoned about by interface types. Using methods effectively will allow you to work with interfaces in your code to improve testability and leaves better organization behind for future readers of your code.

      If you’d like to learn more about the Go programming language in general, check out our How To Code in Go series.



      Source link

      Defining Structs in Go


      Introduction

      Building abstractions around concrete details is the greatest tool that a programming language can give to a developer. Structs allow Go developers to describe the world in which a Go program operates. Instead of reasoning about strings describing a Street, City, or a PostalCode, structs allow us to instead talk about an Address. They serve as a natural nexus for documentation in our efforts to tell future developers (ourselves included) what data is important to our Go programs and how future code should use that data appropriately. Structs can be defined and used in a few different ways. In this tutorial, we’ll take a look at each of these techniques.

      Defining Structs

      Structs work like paper forms that you might use, for example, to file your taxes. Paper forms might have fields for textual pieces of information like your first and last names. Besides text fields, forms might have checkboxes to indicate Boolean values such as “married” or “single,” or date fields for birth date. Similarly, structs collect different pieces of data together and organize them under different field names. When you initialize a variable with a new struct, it’s as though you’ve photocopied a form and made it ready to fill out.

      To create a new struct, you must first give Go a blueprint that describes the fields the struct contains. This struct definition usually begins with the keyword type followed by the name of the struct. After this, use the struct keyword followed by a pair of braces {} where you declare the fields the struct will contain. Once you have defined the struct, you are then able to declare variables that use this struct definition. This example defines a struct and uses it:

      package main
      
      import "fmt"
      
      type Creature struct {
          Name string
      }
      
      func main() {
          c := Creature{
              Name: "Sammy the Shark",
          }
          fmt.Println(c.Name)
      }
      

      When you run this code, you will see this output:

      output

      Sammy the Shark

      We first define a Creature struct in this example, containing a Name field of type string. Within the body of main, we create an instance of Creature by placing a pair of braces after the name of the type, Creature, and then specifying values for that instance’s fields. The instance in c will have its Name field set to “Sammy the Shark”. Within the fmt.Println function invocation, we retrieve the values of the instance’s field by placing a period after the variable where the instance was created, followed by the name of the field we would like to access. For example, c.Name in this case returns the Name field.

      When you declare a new instance of a struct, you generally enumerate the field names with their values, as in the last example. Alternatively, if every field value will be provided during the instantiation of a struct, you can omit the field names, like in this example:

      package main
      
      import "fmt"
      
      type Creature struct {
          Name string
          Type string
      }
      
      func main() {
          c := Creature{"Sammy", "Shark"}
          fmt.Println(c.Name, "the", c.Type)
      }
      

      The output is the same as the last example:

      output

      Sammy the Shark

      We’ve added an extra field to Creature to track the Type of creature as a string. When instantiating Creature within the body of main, we’ve opted to use the shorter instantiation form by providing values for each field in order and omitting their field names. In the declaration Creature{"Sammy", "Shark"}, the Name field takes the value Sammy and the Type field takes the value Shark because Name appears first in the type declaration, followed by Type.

      This shorter declaration form has a few drawbacks that have led the Go community to prefer the longer form in most circumstances. You must provide values for each field in the struct when using the short declaration—you can’t skip fields you don’t care about. This quickly causes short declarations for structs with many fields to become confusing. For this reason, declaring structs using the short form is typically used with structs that have few fields.

      The field names in the examples so far have all begun with capital letters. This is more significant than a stylistic preference. The use of capital or lowercase letters for field names affects whether your field names will be accessible to code running in other packages.

      Struct Field Exporting

      Fields of a struct follow the same exporting rules as other identifiers within the Go programming language. If a field name begins with a capital letter, it will be readable and writeable by code outside of the package where the struct was defined. If the field begins with a lowercase letter, only code within that struct’s package will be able to read and write that field. This example defines fields that are exported and those that are not:

      package main
      
      import "fmt"
      
      type Creature struct {
          Name string
          Type string
      
          password string
      }
      
      func main() {
          c := Creature{
              Name: "Sammy",
              Type: "Shark",
      
              password: "secret",
          }
          fmt.Println(c.Name, "the", c.Type)
          fmt.Println("Password is", c.password)
      }
      

      This will output:

      output

      Sammy the Shark Password is secret

      We added an additional field to our previous examples, secret. secret is an unexported string field, which means that any other package that attempts to instantiate a Creature will not be able to access or set its secret field. Within the same package, we are able to access these fields, as this example has done. Since main is also in the main package, it’s able to reference c.password and retrieve the value stored there. It’s common to have unexported fields in structs with access to them mediated by exported methods.

      Inline Structs

      In addition to defining a new type to represent a struct, you can also define an inline struct. These on-the-fly struct definitions are useful in situations where inventing new names for struct types would be wasted effort. For example, tests often use a struct to define all the parameters that make up a particular test case. It would be cumbersome to come up with new names like CreatureNamePrintingTestCase when that struct is used in only one place.

      Inline struct definitions appear on the right-hand side of a variable assignment. You must provide an instantiation of them immediately after by providing an additional pair of braces with values for each of the fields you define. The example that follows shows an inline struct definition:

      package main
      
      import "fmt"
      
      func main() {
          c := struct {
              Name string
              Type string
          }{
              Name: "Sammy",
              Type: "Shark",
          }
          fmt.Println(c.Name, "the", c.Type)
      }
      

      The output from this example will be:

      output

      Sammy the Shark

      Rather than defining a new type describing our struct with the type keyword, this example defines an inline struct by placing the struct definition immediately following the short-assignment operator, :=. We define the fields of the struct as in previous examples, but then we must immediately supply another pair of braces and the values that each field will assume. Using this struct is now exactly the same as before—we can refer to field names using dot notation. The most common place you will see inline structs declared is during tests, as frequently one-off structs are defined to contain data and expectations for a particular test case.

      Conclusion

      Structs are collections of heterogenous data defined by programmers to organize information. Most programs deal with enormous volumes of data, and without structs, it would become difficult to remember which string or int variables belonged together or which were different. The next time that you find yourself juggling groups of variables, ask yourself if perhaps those variables would be better grouped together using a struct. Those variables may have been describing some higher-level concept all along.



      Source link