반응형

Kubernetes custom controller 개발에 가장 잘 맞는 프로그래밍 언어는 Go 이다. Kubernetes 가 Go 로 개발된 S/W 이다 보니 Custom controller 도 Go 로 만드는 것이 좋을 것 같다는 생각이다.

그래서 겸사겸사 Custom Controller 개발에 필요한 Go 문법만 정리해 보기로 했다.

  • 변수 선언 : var, Short Variable Declaration Operator(:=)
  • Package 선언 및 활용
  • Struct (json 으로 변환)
  • Receiver function
  • Interface 선언, 활용

변수 선언

변수는 var 키워드로 쉽게 선언할 수 있다.

// var 변수명 변수타입
var message string

변수를 선언하면서 값을 대입하면 마지막의 변수 타입은 생략 가능하다.

var message = "Hi, Welcome!"

:= operator 를 사용하면 shortcut 으로 선언하여 var 도 생략할 수 있다.

message := "Hi, Welcome!"

Package 선언 및 활용

모듈로 만들어서 import 하여 사용할 수 있다.

아래와 같이 디렉토리를 만들어보자. greetings 는 모듈로 선언하고 basic 에서 greetings 모듈을 import 하여 사용할 예정이다.

$ mkdir -p go-sample
$ cd go-sample

$ mkdir -p {basic,greetings}
$ cd greetings

go-sample 이라는 프로젝트 아래에 greetings 라는 모듈을 만든다.

모듈을 만들기 위해서 go.mod 파일을 만든다.

$ go mod init github.com/seungkyua/go-sample/greetings

go.mod 파일이 생성되며 파일 내용은 아래와 같다.

module github.com/seungkyua/go-sample/greetings

go 1.19

greetings.go 파일을 만들어서 아래와 같이 입력한다.

여기도 error 핸들링과 string format 을 위해서 모듈을 import 하고 있는데 이를 이해할려고 하지 말고 그냥 Hello function 이 있다는 것만 이해하자.

package greetings

import (
	"errors"
	"fmt"
)

func Hello(name string) (string, error) {
	if name == "" {
		return "", errors.New("empty name")
	}

	message := fmt.Sprintf("Hi, %v. Welcome!", name)
	return message, nil
}

go-sample 홈디렉토리에서 보는 greetings 디렉토리 구조는 아래와 같다.

greetings
├── go.mod
└── greetings.go

golang 1.19 버전 부터는 로컬 하위 디렉토리를 인식하기 위해서 [go.work](<http://go.work>) 를 사용한다. 그리니 아래와 같이 하여 파일을 만들어 보자.

$ go work use ./basic
$ go work use ./greetins

go-sample 홈디렉토리 아래에 [go.work](<http://go.work>) 파일이 아래와 같이 만들어 진다.

go 1.19

use (
	./greetings
	./basic
)

이제 basic 디렉토리로 가서 똑같이 go.mod 파일을 만들고 main.go 파일도 만들어 본다.

$ cd basic

$ go mod init github.com/seungkyua/go-sample/basic

go.mod 파일이 아래와 같이 생성되었음을 알 수 있다.

module github.com/seungkyua/go-sample/basic

go 1.19

greetings 모듈을 로컬로 인식하게 변경한다.

$ go mod edit -replace github.com/seungkyua/go-sample/greetings=../greetings

그리고 로컬 버전을 사용하기 위해서 pseudo 버전을 사용하게 tidy 명령을 활용한다.

$ go mod tidy

그럼 최종 go.mod 는 다음과 같다.

module github.com/seungkyua/go-sample/basic

go 1.19

// go mod edit 으로 로컬 경로를 보도록 수정
replace github.com/seungkyua/go-sample/greetings => ../greetings

// 로컬이므로 pseudo 버전을 만든다 
require github.com/seungkyua/go-sample/greetings v0.0.0-00010101000000-000000000000

main.go 파일을 만들어서 greetings 모듈을 import 하여 활용해 본다.

package main

import (
	"fmt"
	"log"

	"github.com/seungkyua/go-sample/greetings"
)

func main() {
	message, err := greetings.Hello("Seungkyu")
	if err != nil {
		log.Fatal(err)
	}
	fmt.Println(message)
}

go-sample 디렉토리 구조는 아래와 같다.

basic
├── go.mod
└── main.go
greetings
├── go.mod
└── greetings.go
go.work

Struct 활용 (json 변환)

struct 를 json data 로 변환하는 것을 marshal (encoding) 이라고 하고 json data를 struct 로 변환하는 것을 unmarshal (decoding) 이라고 한다.

json 패키지의 Marshal function 은 다음과 같다.

func Marshal(v interface{}) ([]byte, error)

Struct 를 만들어서 json data (byte slice) 로 변환해 보자.

type album struct {
	ID     string  `json:"id"`
	Title  string  `json:"title"`
	Artist string  `json:"artist"`
	Price  float64 `json:"price"`
}

a := album{ID: "1", Title: "Blue Train", Artist: "John Coltrane", Price: 56.99}
b, err := json.Marshal(a)
if err != nil {
	log.Fatal(err)
}

b2 := []byte(`{"id":"1","title":"Blue Train","artist":"John Coltrane","price":56.99}`)
fmt.Println(bytes.Equal(b, b2))

Unmarshal function 은 다음과 같다.

func Unmarshal(data []byte, v interface{}) error

json data (byte slice) 를 struct 로 다시 변환한다.

var a2 album
err = json.Unmarshal(b, &a2)
if err != nil {
	log.Fatal(err)
}
fmt.Println(a2)

Receiver Function

Receiver function 은 struct 의 멤버 변수를 활용할 수 있는 function 이다.

Vertex struct 를 만들고 그에 속한 멤버 변수를 활용하는 function 을 만들면 된다.

func 와 함수명 사이에 Struct 를 변수와 함께 넣으면 Receiver function 이 된다.

type Vertex struct {
	X, Y float64
}

func (v Vertex) Abs() float64 {
	return math.Sqrt(v.X*v.X + v.Y*v.Y)
}

func (v *Vertex) Scale(f float64) {
	v.X = v.X * f
	v.Y = v.Y * f
}

아래와 같이 main 함수를 실행하면 값은 50 이 나온다.

func main() {
	v := Vertex{3, 4}
	v.Scale(10)
	fmt.Println(v.Abs())
}

그런데 만약 위의 func (v *Vertex) Scale(f float64) **에서 Vertex 를 포인터가 아닌 value 로 만들면 어떤 결과가 나올까? 위의 함수에서 * 를 지우고 다시 실행해 보자.

결과는 5 가 된다.

즉, Struct 의 멤버 변수의 값을 바꾸고 싶으면 Pointer Receiver 를 사용해야 한다.

Interface 선언, 활용

Interface 는 method 를 모아둔 것이라 할 수 있다. interface 역시 type 키워드로 선언하기 때문에 interface 타입이라고도 말한다.

아래와 같이 Abs() 메소드를 선언하고 나서 Abs 를 Receiver function 으로 구현했다면 Abs 를 구현한 타입은 Abser 타입이라고 할 수 있다.

type Abser interface {
	Abs() float64
}

아래는 MyFloat 타입도 Abser 타입이라고 할 수 있다. 하지만 Abs 의 Receiver 는 value Receiver 이다.

type MyFloat float64

func (f MyFloat) Abs() float64 {
	if f < 0 {
		return float64(-f)
	}
	return float64(f)
}

Vertex 타입도 역시 Abser 타입이며 pointer Receiver 이다.

type Vertex struct {
	X, Y float64
}

func (v *Vertex) Abs() float64 {
	return math.Sqrt(v.X*v.X + v.Y*v.Y)
}

main 함수에서 interface 를 활용해 본다.

a = v 에서 에러가 발생한다.

func main() {
	var a Abser
	f := MyFloat(-math.Sqrt2)
	v := Vertex{3, 4}

	a = f  // MyFloat 은 Abser 인터페이스를 구현했으니 가능함
	a = &v // *Vertex 는 Abser 인터페이스를 구현했으니 가능함

	a = v  // v 는 Vertext 타입임 (*Vertex 타입이 아님), value receiver 는 구현안했으니 에러 

	fmt.Println(a.Abs())
}

 

Interface 선언, 활용 (2)

interface 는 interface 를 포함할 수 있다.

아래와 같이 Client interface 가 Reader interface 를 가지면 Reader interface 에 선언된 함수가 그 대로 Client 에도 속하게 된다.

package interfaces

import (
	"fmt"
)

type Reader interface {
	Get(obj string) error
	List(list []string) error
}

type Client interface {
	Reader
}

var ReconcileClient Client = &client{}

type client struct {
	name string
}

func (c *client) Get(obj string) error {
	fmt.Println(obj)
	return nil
}

func (c *client) List(list []string) error {
	fmt.Println(list)
	return nil
}

그런 다음 struct 에서 interface 를 또 다시 포함할 수 있다.

아래와 같이 GuestbookReconciler struct 에 Client interface 를 포함하면 GuestbookReconciler 는 마치 Client interface 가 가지는 함수를 자신의 메소드 처럼 사용할 수 있다.

package main

import "github.com/seungkyua/go-sample/interfaces"

type GuestbookReconciler struct {
	interfaces.Client
}

func main() {
	g := GuestbookReconciler{interfaces.ReconcileClient}
	g.Get("Seungkyu")
	g.Client.Get("Seungkyu")
}

이 때 메소드 활용은 g.Get 혹은 g.Client.Get 둘 다 가능하다.

반응형
Posted by seungkyua@gmail.com

댓글을 달아 주세요

반응형
I. require 와 include
    1. 다른 파일을 불러온다. HTML 태그, php 함수, php 클래스 등이다.
    2. require() 는 실패했을 경우 치명적인 오류를 내지만 include() 는 가벼운 경고만 낸다.
    3. require_once()와 include_once() 는 라이브러리 파일을 두 번 이상 포함시키는 일을 막아준다.
        그러나 require()나 include() 보다 속도가 느리다. 
    4. php 문은 파일 확장자가 .html 이거나 .php 등일 때에만 스크립트 언어로 처리되어 실행.
    5. require로 불러들일 파일의 확장자 이름에 .inc와 같은 특별한 규칙을 두어 작성하는 것도 좋은 방법
        단, 이런 문서를 웹 문서 트리 안에 저장할 때에는 사용자들이 직접 접근 하지 못하도록 주위 (패스워드 등) 
        php 환경설정에서 아래와 같이 디렉토리를 웹 문서 디렉토리로 설정하고 추가로 config 설정파일은
        php 웹 문서가 아닌 디렉토리로 지정할 수 있다.(/Library/WebServer/php/includes)
        예) include_path = ".:/Library/WebServer/php/includes:/Library/WebServer/Documents"

II. 참조 전달 방법
    1. & 를 이용하면 참조로 전달 할 수 있다.
function increment(&$value, $amount = 1) {
    $value = $value + $amount;

III. 클래스 만들기
    1. 생성자와 소멸자
        - 생성자는 __construct() 라는 특별한 이름으로 파라미터를 가지며 생성할 수 있다.
        - 소멸자는 __destruct() 라는 이름으로 만들며 파라미터를 가지지 못한다.
 
    2. 클래스 속성
        - 접근자가 생략되면 모두 public 이다.
        - 클래스 속성의 접근자는 private 으로 하고 __set(), __get() 으로 속성에 접근한다.
        - 자기 자신의 인스턴스는 $this 로 접근한다.

class Point {

    private $x;

    private $y;

    public function __construct($x, $y) {

        $this->x = $x;

        $this->y = $y;

    }

    public function __get($name) {

        return $this->$name;

    }

    public function __set($name, $value) {

        $this->$name = $value;

    }

    public function display() {

        print("\$this->x = " . $this->x . "<br/>");

        print("\$this->y = " . $this->y . "<br/>");

    } 

}

$point = new Point(3, 5);

print($point->x);         // 3

print($point->y);         // 5

$point->x = 7;

print($point->x);         // 7

$point->display(); 


    3. 클래스 상속
        - 부모의 클래스는  parent 키워드를 이용한다.

class Rectangle extends Point {

    private $width;

    private $height;


    public function __construct($x, $y, $width, $height) {

        parent::__construct($x, $y);

        $this->x = $x;

        $this->y = $y;

    }

}

$rectangle = new Rectangle(10, 20, 100, 200);

print($rectangle->x);   // 10

 
    4. 인터페이스
        - 인터페이스는 interface 키워드를 사용한다.
        - 구현은 implements 로 한다.

interface Displayable {

    public function display();

}

class WebPage implements Displayable {

    public function display() {

        print("Hello World!! <br/>");

    }

}

$page = new WebPage();

$page->display();


    5. 클래스의 상수 및 정적 메소드 
        - 상수는 const 키워드를 사용하고 :: 연산자로 접근한다.
        - 정적 메소드는 static 키워드를 사용하고 :: 연산자로 접근한다.

class Math {

    const pi = 3.14159;

    public static function squared($input) {

        return $input * $input;

    }

}

print("Math::pi = " . Math::pi . "<br/>");

print("Math::squared(8) = " . Math::squared(8) . "<br/>");


    6. 클래스 타입 hinting
        - 클래스의 특정 타입을 파라미터 전달 시에 전달할 수 있다.
function check_hint(Point $object) {
    // ...

    7.  추상 클래스
        - abstract 키워드를 사용한다.

abstract class A {

    public abstract function operationX($param1, $param2);

}

class ConcreteA extends A {

    public function operationX($param1, $param2) {

        return $param1 + $param2;

    }

}

$a = new ConcreteA();

print($a->operationX(3, 4) . "<br/>");


    8. 메소드 오버로딩
        - __call() 메소드를 이용하여 오버로딩을 구현한다.
        - 첫번째 전달인자는 호출되는 메소드이고, 두번째 전달인자는 파라미터로 넘길 파라미터 배열이다.
public function __call($method, $param) {
    if ($method == "display") {
        if (is_object($p[0])) {
            $this->displayObject($p[0]);
        } else if (is_array($p[0])) {
            $this->displayArray($p[0]);
        } else {
            $this->displayScalar($p[0]);
        }
    }
}

$object = new Overload();
$object->display(array(1, 2, 3));
$object->display('cat'); 

    9. 클래스 자동 import
        - __autoload() 함수를 이용하여 자동으로 include 할 수 있다.
        - 이 함수는 클래스 메소드가 아니라 독립된 함수이다.
        - 아래는 클래스 파일과 같은 파일명을 읽어 들이는 예제이다.
function __autoload($name) {
    include_once $name . ".php";

    10. Reflection API
require_once("config.inc");

$class = new ReflectionClass("Point");
print("<pre>" . $class . "</pre>"); 


반응형
Posted by seungkyua@gmail.com

댓글을 달아 주세요