github.com/asynkron/protoactor-go@v0.0.0-20240308120642-ef91a6abee75/protobuf/protoc-gen-go-grain/templates/grain.tmpl (about)

     1  {{ $service := . }}
     2  var x{{ $service.Name }}Factory func() {{ $service.Name }}
     3  
     4  // {{ $service.Name }}Factory produces a {{ $service.Name }}
     5  func {{ $service.Name }}Factory(factory func() {{ $service.Name }}) {
     6  	x{{ $service.Name }}Factory = factory
     7  }
     8  
     9  // Get{{ $service.Name }}GrainClient instantiates a new {{ $service.Name }}GrainClient with given Identity
    10  func Get{{ $service.Name }}GrainClient(c *cluster.Cluster, id string) *{{ $service.Name }}GrainClient {
    11  	if c == nil {
    12  		panic(fmt.Errorf("nil cluster instance"))
    13  	}
    14  	if id == "" {
    15  		panic(fmt.Errorf("empty id"))
    16  	}
    17  	return &{{ $service.Name }}GrainClient{Identity: id, cluster: c}
    18  }
    19  
    20  // Get{{ $service.Name }}Kind instantiates a new cluster.Kind for {{ $service.Name }}
    21  func Get{{ $service.Name }}Kind(opts ...actor.PropsOption) *cluster.Kind {
    22  	props := actor.PropsFromProducer(func() actor.Actor {
    23  		return &{{ $service.Name }}Actor{
    24  			Timeout: 60 * time.Second,
    25  		}
    26  	}, opts...)
    27  	kind := cluster.NewKind("{{ $service.Name }}", props)
    28  	return kind
    29  }
    30  
    31  // Get{{ $service.Name }}Kind instantiates a new cluster.Kind for {{ $service.Name }}
    32  func New{{ $service.Name }}Kind(factory func() {{ $service.Name }}, timeout time.Duration, opts ...actor.PropsOption) *cluster.Kind {
    33  	x{{ $service.Name }}Factory = factory
    34  	props := actor.PropsFromProducer(func() actor.Actor {
    35  		return &{{ $service.Name }}Actor{
    36  			Timeout: timeout,
    37  		}
    38  	}, opts...)
    39  	kind := cluster.NewKind("{{ $service.Name }}", props)
    40  	return kind
    41  }
    42  
    43  // {{ $service.Name }} interfaces the services available to the {{ $service.Name }}
    44  type {{ $service.Name }} interface {
    45  	Init(ctx cluster.GrainContext)
    46  	Terminate(ctx cluster.GrainContext)
    47  	ReceiveDefault(ctx cluster.GrainContext)
    48  	{{- range $method := .Methods }}
    49  	{{ if $method.Options.Reenterable -}}
    50  	{{ $method.Name }}(req *{{ $method.Input }}, respond func(*{{ $method.Output }}), onError func(error), ctx cluster.GrainContext) error
    51  	{{- else -}}
    52  	{{ $method.Name }}(req *{{ $method.Input }}, ctx cluster.GrainContext) (*{{ $method.Output }}, error)
    53  	{{- end }}
    54  	{{- end}}
    55  }
    56  
    57  // {{ $service.Name }}GrainClient holds the base data for the {{ $service.Name }}Grain
    58  type {{ $service.Name }}GrainClient struct {
    59  	Identity string
    60  	cluster  *cluster.Cluster
    61  }
    62  {{ range $method := .Methods}}
    63  {{ if $method.Options.Future -}}
    64  // {{ $method.Name }}Future return a future for the execution of {{ $method.Name }} on the cluster
    65  func (g *{{ $service.Name }}GrainClient) {{ $method.Name }}Future(r *{{ $method.Input }}, opts ...cluster.GrainCallOption) (*actor.Future, error) {
    66  	bytes, err := proto.Marshal(r)
    67  	if err != nil {
    68  		return nil, err
    69  	}
    70  
    71  	reqMsg := &cluster.GrainRequest{MethodIndex: {{ $method.Index }}, MessageData: bytes}
    72  	f, err := g.cluster.RequestFuture(g.Identity, "{{ $service.Name }}", reqMsg, opts...)
    73  	if err != nil {
    74  		return nil, fmt.Errorf("error request future: %w", err)
    75  	}
    76  
    77  	return f, nil
    78  }
    79  {{ end }}
    80  // {{ $method.Name }} requests the execution on to the cluster with CallOptions
    81  func (g *{{ $service.Name }}GrainClient) {{ $method.Name }}(r *{{ $method.Input }}, opts ...cluster.GrainCallOption) (*{{ $method.Output }}, error) {
    82  	bytes, err := proto.Marshal(r)
    83  	if err != nil {
    84  		return nil, err
    85  	}
    86  	reqMsg := &cluster.GrainRequest{MethodIndex: {{ $method.Index }}, MessageData: bytes}
    87  	resp, err := g.cluster.Request(g.Identity, "{{ $service.Name }}", reqMsg, opts...)
    88  	if err != nil {
    89  		return nil, fmt.Errorf("error request: %w", err)
    90  	}
    91  	switch msg := resp.(type) {
    92  	case *{{ $method.Output }}:
    93  		return msg, nil
    94  	case *cluster.GrainErrorResponse:
    95  		if msg == nil {
    96  			return nil, nil
    97  		}
    98  		return nil, msg
    99  	default:
   100  		return nil, fmt.Errorf("unknown response type %T", resp)
   101  	}
   102  }
   103  {{ end }}
   104  // {{ $service.Name }}Actor represents the actor structure
   105  type {{ $service.Name }}Actor struct {
   106  	ctx     cluster.GrainContext
   107  	inner   {{ $service.Name }}
   108  	Timeout time.Duration
   109  }
   110  
   111  // Receive ensures the lifecycle of the actor for the received message
   112  func (a *{{ $service.Name }}Actor) Receive(ctx actor.Context) {
   113  	switch msg := ctx.Message().(type) {
   114  	case *actor.Started: //pass
   115  	case *cluster.ClusterInit:
   116  		a.ctx = cluster.NewGrainContext(ctx, msg.Identity, msg.Cluster)
   117  		a.inner = x{{ $service.Name }}Factory()
   118  		a.inner.Init(a.ctx)
   119  
   120  		if a.Timeout > 0 {
   121  			ctx.SetReceiveTimeout(a.Timeout)
   122  		}
   123  	case *actor.ReceiveTimeout:
   124  		ctx.Poison(ctx.Self())
   125  	case *actor.Stopped:
   126  		a.inner.Terminate(a.ctx)
   127  	case actor.AutoReceiveMessage: // pass
   128  	case actor.SystemMessage: // pass
   129  
   130  	case *cluster.GrainRequest:
   131  		switch msg.MethodIndex {
   132  		{{ range $method := .Methods -}}
   133  		case {{ $method.Index }}:
   134  			req := &{{ $method.Input }}{}
   135  			err := proto.Unmarshal(msg.MessageData, req)
   136  			if err != nil {
   137  				ctx.Logger().Error("[Grain] {{ $method.Name }}({{ $method.Input }}) proto.Unmarshal failed.", slog.Any("error", err))
   138  				resp := cluster.NewGrainErrorResponse(cluster.ErrorReason_INVALID_ARGUMENT, err.Error()).
   139  					WithMetadata(map[string]string{
   140  						"argument": req.String(),
   141  				})
   142  				ctx.Respond(resp)
   143  				return
   144  			}
   145  			{{ if $method.Options.Reenterable -}}
   146  			err = a.inner.{{ $method.Name }}(req, respond[*{{ $method.Output }}](a.ctx), a.onError, a.ctx)
   147  			{{ else }}
   148  			r0, err := a.inner.{{ $method.Name }}(req, a.ctx)
   149  			{{ end -}}
   150  			if err != nil {
   151  				resp := cluster.FromError(err)
   152  				ctx.Respond(resp)
   153  				return
   154  			}
   155  			{{ if not $method.Options.Reenterable -}}
   156  			ctx.Respond(r0)
   157  			{{ end -}}
   158  		{{ end -}}
   159  		}
   160  	default:
   161  		a.inner.ReceiveDefault(a.ctx)
   162  	}
   163  }
   164  
   165  // onError should be used in ctx.ReenterAfter
   166  // you can just return error in reenterable method for other errors
   167  func (a *{{ $service.Name }}Actor) onError(err error) {
   168  	resp := cluster.FromError(err)
   169  	a.ctx.Respond(resp)
   170  }