Repository: Tochemey/goakt
Pull request #29 (2023-02-18 14:20:36)

feat: add-ons

codecov[bot]: # [Codecov](https://codecov.io/gh/Tochemey/goakt/pull/29?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Arsene) Report
Base: **57.33**% // Head: **56.29**% // Decreases project coverage by **`-1.04%`** :warning:
> Coverage data is based on head [(`70afbff`)](https://codecov.io/gh/Tochemey/goakt/pull/29?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Arsene) compared to base [(`c7507a5`)](https://codecov.io/gh/Tochemey/goakt/commit/c7507a597ccb1c82f524d0197858ee9663207ad4?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Arsene).
> Patch coverage: 44.56% of modified lines in pull request are covered.

<details><summary>Additional details and impacted files</summary>


```diff
@@            Coverage Diff             @@
##             main      #29      +/-   ##
==========================================
- Coverage   57.33%   56.29%   -1.04%     
==========================================
  Files          40       42       +2     
  Lines        3628     3812     +184     
==========================================
+ Hits         2080     2146      +66     
- Misses       1411     1519     +108     
- Partials      137      147      +10     
```


| [Impacted Files](https://codecov.io/gh/Tochemey/goakt/pull/29?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Arsene) | Coverage Δ | |
|---|---|---|
| [actors/api.go](https://codecov.io/gh/Tochemey/goakt/pull/29?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Arsene#diff-YWN0b3JzL2FwaS5nbw==) | `41.17% <0.00%> (-41.72%)` | :arrow_down: |
| [actors/errors.go](https://codecov.io/gh/Tochemey/goakt/pull/29?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Arsene#diff-YWN0b3JzL2Vycm9ycy5nbw==) | `0.00% <0.00%> (ø)` | |
| [actors/pid.go](https://codecov.io/gh/Tochemey/goakt/pull/29?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Arsene#diff-YWN0b3JzL3BpZC5nbw==) | `55.93% <ø> (ø)` | |
| [actors/types.go](https://codecov.io/gh/Tochemey/goakt/pull/29?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Arsene#diff-YWN0b3JzL3R5cGVzLmdv) | `92.85% <ø> (ø)` | |
| [actors/reflection.go](https://codecov.io/gh/Tochemey/goakt/pull/29?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Arsene#diff-YWN0b3JzL3JlZmxlY3Rpb24uZ28=) | `62.50% <62.50%> (ø)` | |
| [actors/types\_loader.go](https://codecov.io/gh/Tochemey/goakt/pull/29?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Arsene#diff-YWN0b3JzL3R5cGVzX2xvYWRlci5nbw==) | `81.70% <81.70%> (ø)` | |
| [modules/projection/projection.go](https://codecov.io/gh/Tochemey/goakt/pull/29?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Arsene#diff-bW9kdWxlcy9wcm9qZWN0aW9uL3Byb2plY3Rpb24uZ28=) | `74.29% <0.00%> (-4.68%)` | :arrow_down: |
| [modules/persistence/plugins/memory/offset\_store.go](https://codecov.io/gh/Tochemey/goakt/pull/29?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Arsene#diff-bW9kdWxlcy9wZXJzaXN0ZW5jZS9wbHVnaW5zL21lbW9yeS9vZmZzZXRfc3RvcmUuZ28=) | `70.33% <0.00%> (-2.55%)` | :arrow_down: |
| [...odules/persistence/plugins/memory/journal\_store.go](https://codecov.io/gh/Tochemey/goakt/pull/29?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Arsene#diff-bW9kdWxlcy9wZXJzaXN0ZW5jZS9wbHVnaW5zL21lbW9yeS9qb3VybmFsX3N0b3JlLmdv) | `71.05% <0.00%> (-1.13%)` | :arrow_down: |

Help us with your feedback. Take ten seconds to tell us [how you rate us](https://about.codecov.io/nps?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Arsene). Have a feature suggestion? [Share it here.](https://app.codecov.io/gh/feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Arsene)

</details>

[:umbrella: View full report at Codecov](https://codecov.io/gh/Tochemey/goakt/pull/29?src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Arsene).   
:loudspeaker: Do you have feedback about the report comment? [Let us know in this issue](https://about.codecov.io/codecov-pr-comment-feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Arsene).


Patch:
diff --git a/actors/api.go b/actors/api.go
index 904871f..2f41e24 100644
--- a/actors/api.go
+++ b/actors/api.go
@@ -2,10 +2,17 @@ package actors
 
 import (
 	"context"
+	"fmt"
 	"sync"
 	"time"
 
+	pb "github.com/tochemey/goakt/pb/goakt/v1"
+	"github.com/tochemey/goakt/pkg/grpc"
+	"github.com/tochemey/goakt/telemetry"
+	"google.golang.org/grpc/codes"
+	"google.golang.org/grpc/status"
 	"google.golang.org/protobuf/proto"
+	"google.golang.org/protobuf/types/known/anypb"
 )
 
 // SendSync sends a synchronous message to another actor and expect a response.
@@ -99,3 +106,97 @@ func SendAsync(ctx context.Context, to PID, message proto.Message) error {
 
 	return nil
 }
+
+// RemoteSendAsync sends a message to an actor remotely without expecting any reply
+func RemoteSendAsync(ctx context.Context, to *pb.Address, message proto.Message) error {
+	// add a span context
+	ctx, span := telemetry.SpanContext(ctx, "RemoteSendAsync")
+	defer span.End()
+
+	// marshal the message
+	marshaled, err := anypb.New(message)
+	if err != nil {
+		return err
+	}
+
+	// create an instance of remote client service
+	rpcConn, _ := grpc.GetClientConn(ctx, fmt.Sprintf("%s:%d", to.GetHost(), to.GetPort()))
+	remoteClient := pb.NewRemotingServiceClient(rpcConn)
+	// prepare the rpcRequest to send
+	request := &pb.RemoteSendAsyncRequest{
+		Address: to,
+		Message: marshaled,
+	}
+	// send the message and handle the error in case there is any
+	if _, err := remoteClient.RemoteSendAsync(ctx, request); err != nil {
+		return err
+	}
+	return nil
+}
+
+// RemoteSendSync sends a synchronous message to another actor remotely and expect a response.
+func RemoteSendSync(ctx context.Context, to *pb.Address, message proto.Message) (response proto.Message, err error) {
+	// add a span context
+	ctx, span := telemetry.SpanContext(ctx, "RemoteSendAsync")
+	defer span.End()
+
+	// marshal the message
+	marshaled, err := anypb.New(message)
+	if err != nil {
+		return nil, err
+	}
+
+	// construct the from address
+	from := RemoteNoSender
+
+	// create an instance of remote client service
+	rpcConn, _ := grpc.GetClientConn(ctx, fmt.Sprintf("%s:%d", to.GetHost(), to.GetPort()))
+	remoteClient := pb.NewRemotingServiceClient(rpcConn)
+	// prepare the rpcRequest to send
+	rpcRequest := &pb.RemoteSendSyncRequest{
+		Sender:   from,
+		Receiver: to,
+		Message:  marshaled,
+	}
+	// send the request
+	rpcResponse, rpcErr := remoteClient.RemoteSendSync(ctx, rpcRequest)
+	// handle the error
+	if rpcErr != nil {
+		return nil, rpcErr
+	}
+
+	return rpcResponse.GetMessage(), nil
+}
+
+// RemoteLookup look for an actor address on a remote node.
+func RemoteLookup(ctx context.Context, host string, port int, name string, actorSystem string) (addr *pb.Address, err error) {
+	// add a span context
+	ctx, span := telemetry.SpanContext(ctx, "RemoteLookup")
+	defer span.End()
+
+	// create an instance of remote client service
+	rpcConn, _ := grpc.GetClientConn(ctx, fmt.Sprintf("%s:%d", host, port))
+	remoteClient := pb.NewRemotingServiceClient(rpcConn)
+
+	// prepare the request to send
+	request := &pb.RemoteLookupRequest{
+		ActorSystem: actorSystem,
+		Host:        host,
+		Port:        int32(port),
+		Name:        name,
+	}
+	// send the message and handle the error in case there is any
+	response, err := remoteClient.RemoteLookup(ctx, request)
+	// we know the error will always be a grpc error
+	if err != nil {
+		// get the status error
+		s := status.Convert(err)
+		if s.Code() == codes.NotFound {
+			return nil, nil
+		}
+		return nil, err
+	}
+
+	// return the response
+	return response.GetAddress(), nil
+}
diff --git a/actors/errors.go b/actors/errors.go
index e3c8315..b710e78 100644
--- a/actors/errors.go
+++ b/actors/errors.go
@@ -1,8 +1,9 @@
 package actors
 
 import (
-	"errors"
+	"fmt"
 
+	"github.com/pkg/errors"
 	"google.golang.org/grpc/codes"
 	"google.golang.org/grpc/status"
 )
@@ -19,4 +20,7 @@ var (
 	ErrRemoteSendInvalidNode        = status.Error(codes.FailedPrecondition, "invalid actor system node")
 	ErrRemoteActorNotFound          = func(addr string) error { return status.Errorf(codes.NotFound, "remote actor=%s not found", addr) }
 	ErrRemoteSendFailure            = func(err error) error { return status.Error(codes.Internal, err.Error()) }
+	ErrInstanceNotAnActor           = errors.New("failed to create instance. Reason: instance does not implement the Actor interface")
+	ErrInvalidInstance              = errors.New("failed to create instance. Reason: invalid instance")
+	ErrTypeNotFound                 = func(typeName string) error { return fmt.Errorf("typeName=%s not found", typeName) }
 )
diff --git a/actors/passivation.go b/actors/passivation.go
deleted file mode 100644
index 97e8f21..0000000
--- a/actors/passivation.go
+++ /dev/null
@@ -1,63 +0,0 @@
-package actors
-
-import (
-	"context"
-	"time"
-)
-
-// passivationListener checks whether the actor is processing messages or not.
-// when the actor is idle, it automatically shuts down to free resources
-func (p *pid) passivationListener() {
-	p.logger.Info("start the passivation listener...")
-	// create the ticker
-	ticker := time.NewTicker(p.passivateAfter)
-	// create the stop ticker signal
-	tickerStopSig := make(chan Unit, 1)
-
-	// start ticking
-	go func() {
-		for {
-			select {
-			case <-ticker.C:
-				// check whether the actor is idle or not
-				idleTime := time.Since(p.lastProcessingTime.Load())
-				// check whether the actor is idle
-				if idleTime >= p.passivateAfter {
-					// set the done channel to stop the ticker
-					tickerStopSig <- Unit{}
-					return
-				}
-			case <-p.haltPassivationLnr:
-				// set the done channel to stop the ticker
-				tickerStopSig <- Unit{}
-				return
-			}
-		}
-	}()
-	// wait for the stop signal to stop the ticker
-	<-tickerStopSig
-	// stop the ticker
-	ticker.Stop()
-	// only passivate when actor is alive
-	if !p.IsOnline() {
-		// add some logging info
-		p.logger.Infof("Actor=%s is offline. No need to passivate", p.ActorPath().String())
-		return
-	}
-
-	// add some logging info
-	p.logger.Infof("Passivation mode has been triggered for actor=%s...", p.ActorPath().String())
-	// passivate the actor
-	p.passivate()
-}
-
-func (p *pid) passivate() {
-	// create a context
-	ctx := context.Background()
-	// stop the actor PID
-	p.stop(ctx)
-	// perform some cleanup with the actor
-	if err := p.Actor.PostStop(ctx); err != nil {
-		panic(err)
-	}
-}
diff --git a/actors/pid.go b/actors/pid.go
index 87cab1d..a9c2690 100644
--- a/actors/pid.go
+++ b/actors/pid.go
@@ -21,9 +21,6 @@ import (
 	"google.golang.org/protobuf/types/known/anypb"
 )
 
-// NoSender means that there is no sender
-var NoSender PID
-
 type watchMan struct {
 	Parent  PID        // the Parent of the actor watching
 	ErrChan chan error // the channel where to pass error message
@@ -934,3 +931,60 @@ func (p *pid) supervise(cid PID, watcher *watchMan) {
 		}
 	}
 }
+
+// passivationListener checks whether the actor is processing messages or not.
+// when the actor is idle, it automatically shuts down to free resources
+func (p *pid) passivationListener() {
+	p.logger.Info("start the passivation listener...")
+	// create the ticker
+	ticker := time.NewTicker(p.passivateAfter)
+	// create the stop ticker signal
+	tickerStopSig := make(chan Unit, 1)
+
+	// start ticking
+	go func() {
+		for {
+			select {
+			case <-ticker.C:
+				// check whether the actor is idle or not
+				idleTime := time.Since(p.lastProcessingTime.Load())
+				// check whether the actor is idle
+				if idleTime >= p.passivateAfter {
+					// set the done channel to stop the ticker
+					tickerStopSig <- Unit{}
+					return
+				}
+			case <-p.haltPassivationLnr:
+				// set the done channel to stop the ticker
+				tickerStopSig <- Unit{}
+				return
+			}
+		}
+	}()
+	// wait for the stop signal to stop the ticker
+	<-tickerStopSig
+	// stop the ticker
+	ticker.Stop()
+	// only passivate when actor is alive
+	if !p.IsOnline() {
+		// add some logging info
+		p.logger.Infof("Actor=%s is offline. No need to passivate", p.ActorPath().String())
+		return
+	}
+
+	// add some logging info
+	p.logger.Infof("Passivation mode has been triggered for actor=%s...", p.ActorPath().String())
+	// passivate the actor
+	p.passivate()
+}
+
+func (p *pid) passivate() {
+	// create a context
+	ctx := context.Background()
+	// stop the actor PID
+	p.stop(ctx)
+	// perform some cleanup with the actor
+	if err := p.Actor.PostStop(ctx); err != nil {
+		panic(err)
+	}
+}
diff --git a/actors/types.go b/actors/pid_map.go
similarity index 81%
rename from actors/types.go
rename to actors/pid_map.go
index 284ce24..82e656a 100644
--- a/actors/types.go
+++ b/actors/pid_map.go
@@ -1,9 +1,20 @@
 package actors
 
-import "sync"
+import (
+	"sync"
 
+	pb "github.com/tochemey/goakt/pb/goakt/v1"
+)
+
+// Unit type
 type Unit struct{}
 
+// NoSender means that there is no sender
+var NoSender PID
+
+// RemoteNoSender means that there is no sender
+var RemoteNoSender = new(pb.Address)
+
 type pidMap struct {
 	mu   sync.Mutex
 	pids map[string]PID
diff --git a/actors/reflection.go b/actors/reflection.go
new file mode 100644
index 0000000..3480531
--- /dev/null
+++ b/actors/reflection.go
@@ -0,0 +1,55 @@
+package actors
+
+import (
+	"reflect"
+)
+
+// Reflection helps create an instance dynamically
+type Reflection interface {
+	// ActorOf creates a new instance of Actor from its concrete type
+	ActorOf(rtype reflect.Type) (actor Actor, err error)
+	// ActorFrom creates a new instance of Actor from its FQN
+	ActorFrom(name string) (actor Actor, err error)
+}
+
+// reflection implements Reflection
+type reflection struct {
+	typesLoader TypesLoader
+}
+
+// enforce compilation error
+var _ Reflection = &reflection{}
+
+// NewReflection creates an instance of Reflection
+func NewReflection(loader TypesLoader) Reflection {
+	return &reflection{typesLoader: loader}
+}
+
+// ActorOf creates a new instance of an Actor
+func (r *reflection) ActorOf(rtype reflect.Type) (actor Actor, err error) {
+	// grab the Actor interface type
+	iface := reflect.TypeOf((*Actor)(nil)).Elem()
+	// make sure the type implements Actor interface
+	isActor := rtype.Implements(iface) || reflect.PtrTo(rtype).Implements(iface)
+	// reject the creation of the instance
+	if !isActor {
+		return nil, ErrInstanceNotAnActor
+	}
+	// get the type value of the object type
+	typVal := reflect.New(rtype)
+	// validate the typVal
+	if !typVal.IsValid() {
+		return nil, ErrInvalidInstance
+	}
+	return typVal.Interface().(Actor), nil
+}
+
+// ActorFrom creates a new instance of Actor from its FQN
+func (r *reflection) ActorFrom(name string) (actor Actor, err error) {
+	// grab the type from the typesLoader
+	rtype, ok := r.typesLoader.TypeByName(name)
+	if !ok {
+		return nil, ErrTypeNotFound(name)
+	}
+	return r.ActorOf(rtype)
+}
diff --git a/actors/types_loader.go b/actors/types_loader.go
new file mode 100644
index 0000000..57bab6d
--- /dev/null
+++ b/actors/types_loader.go
@@ -0,0 +1,143 @@
+package actors
+
+import (
+	"fmt"
+	"reflect"
+	"sync"
+)
+
+// TypesLoader represents reflection typesLoader for dynamic loading and creation of
+// actors at run-time
+type TypesLoader interface {
+	// Register an object with its fully qualified name
+	Register(name string, v any)
+	// Type returns the type of object,
+	Type(v any) (reflect.Type, bool)
+	// TypeByName returns the type of object given its name
+	TypeByName(name string) (reflect.Type, bool)
+	// Parent represents the parent typesLoader
+	Parent() TypesLoader
+}
+
+// typesLoader implements TypesLoader
+type typesLoader struct {
+	parent TypesLoader
+	names  map[string]reflect.Type
+	types  map[string]reflect.Type
+	mu     sync.Mutex
+}
+
+// enforce compilation error
+var _ TypesLoader = &typesLoader{}
+
+// NewTypesLoader creates an instance of TypesLoader
+func NewTypesLoader(parent TypesLoader) TypesLoader {
+	l := &typesLoader{
+		parent: nil,
+		names:  make(map[string]reflect.Type),
+		types:  make(map[string]reflect.Type),
+		mu:     sync.Mutex{},
+	}
+	l.SetParent(parent)
+	return l
+}
+
+// Register an object with its fully qualified path
+func (l *typesLoader) Register(name string, v any) {
+	// define a variable to hold the object type
+	var vType reflect.Type
+	// pattern match on the object type
+	switch _type := v.(type) {
+	case reflect.Type:
+		vType = _type
+	default:
+		vType = reflect.TypeOf(v).Elem()
+	}
+
+	// construct the type package path
+	path := fmt.Sprintf("%s.%s", vType.PkgPath(), vType.Name())
+	// set name to path if name is not set
+	if len(name) == 0 {
+		name = path
+	}
+	// only register the type when it is not set registered
+	if _, exist := l.Type(v); !exist {
+		// acquire the lock
+		l.mu.Lock()
+		l.types[path] = vType
+		l.mu.Unlock()
+	}
+	if _, exist := l.TypeByName(name); !exist {
+		l.mu.Lock()
+		l.names[name] = vType
+		l.mu.Unlock()
+	}
+}
+
+// Type returns the type of object
+func (l *typesLoader) Type(v any) (reflect.Type, bool) {
+	// acquire the lock
+	l.mu.Lock()
+	// release the lock
+	defer l.mu.Unlock()
+
+	// grab the object type
+	vType := reflect.TypeOf(v).Elem()
+	// construct the qualified name
+	path := fmt.Sprintf("%s.%s", vType.PkgPath(), vType.Name())
+	// lookup the type in the typesLoader registry
+	t, ok := l.types[path]
+	// if not found check whether the parent has it
+	if !ok {
+		// if the parent is not nil lookup for the type
+		if l.parent != nil {
+			return l.parent.Type(v)
+		}
+	}
+	// if ok return it
+	return t, ok
+}
+
+// TypeByName returns the type of object given the name
+func (l *typesLoader) TypeByName(name string) (reflect.Type, bool) {
+	// acquire the lock
+	l.mu.Lock()
+	// release the lock
+	defer l.mu.Unlock()
+
+	// grab the type from the existing names
+	t, ok := l.names[name]
+	// if not found check with the parent typesLoader
+	if !ok {
+		// if the parent is not nil lookup for the type
+		if l.parent != nil {
+			return l.parent.TypeByName(name)
+		}
+	}
+	// if ok return it
+	return t, ok
+}
+
+// Parent returns the typesLoader parent
+func (l *typesLoader) Parent() TypesLoader {
+	return l.parent
+}
+
+// SetParent sets the typesLoader
+func (l *typesLoader) SetParent(parent TypesLoader) {
+	// check whether the parent is nil
+	if parent == nil {
+		return
+	}
+
+	// if the parent is the same as the given typesLoader return
+	if parent == l {
+		return
+	}
+
+	// only set when the parent is not nil
+	if l.parent == nil {
+		l.parent = parent
+		return
+	}
+}


Test patch:
diff --git a/actors/reflection_test.go b/actors/reflection_test.go
new file mode 100644
index 0000000..6e573f1
--- /dev/null
+++ b/actors/reflection_test.go
@@ -0,0 +1,49 @@
+package actors
+
+import (
+	"testing"
+
+	"github.com/stretchr/testify/assert"
+)
+
+func TestReflection(t *testing.T) {
+	t.Run("With ActorFrom", func(t *testing.T) {
+		tl := NewTypesLoader(nil)
+		// create an instance of an actor
+		actor := NewTestActor()
+		// register the actor into the types registry
+		tl.Register("TestActor", actor)
+
+		// create an instance of reflection
+		reflection := NewReflection(tl)
+
+		// create an instance of test actor from the string TestActor
+		actual, err := reflection.ActorFrom("TestActor")
+		assert.NoError(t, err)
+		assert.NotNil(t, actual)
+		assert.IsType(t, new(TestActor), actual)
+	})
+
+	t.Run("With ActorOf", func(t *testing.T) {
+		tl := NewTypesLoader(nil)
+		// create an instance of an actor
+		actor := NewTestActor()
+		// register the actor into the types registry
+		tl.Register("", actor)
+
+		// create an instance of reflection
+		reflection := NewReflection(tl)
+
+		rtype, ok := tl.Type(actor)
+		assert.True(t, ok)
+		assert.NotNil(t, rtype)
+
+		// create an instance of test actor from the string TestActor
+		actual, err := reflection.ActorOf(rtype)
+
+		// perform some assertions
+		assert.NoError(t, err)
+		assert.NotNil(t, actual)
+		assert.IsType(t, new(TestActor), actual)
+	})
+}
diff --git a/actors/types_loader_test.go b/actors/types_loader_test.go
new file mode 100644
index 0000000..bb7c3ae
--- /dev/null
+++ b/actors/types_loader_test.go
@@ -0,0 +1,43 @@
+package actors
+
+import (
+	"testing"
+
+	"github.com/stretchr/testify/assert"
+)
+
+func TestTypesLoader(t *testing.T) {
+	t.Run("With new instance", func(t *testing.T) {
+		tl := NewTypesLoader(nil)
+		var p any = tl
+		_, ok := p.(TypesLoader)
+		assert.True(t, ok)
+		assert.Nil(t, tl.Parent())
+	})
+
+	t.Run("With registration with name", func(t *testing.T) {
+		tl := NewTypesLoader(nil)
+		// create an instance of an object
+		actor := NewTestActor()
+		// register that actor
+		tl.Register("TestActor", actor)
+		// assert the type of TestActor
+		_, ok := tl.TypeByName("TestActor")
+		assert.True(t, ok)
+		_, ok = tl.Type(actor)
+		assert.True(t, ok)
+	})
+
+	t.Run("With registration without name", func(t *testing.T) {
+		tl := NewTypesLoader(nil)
+		// create an instance of an object
+		actor := NewTestActor()
+		// register that actor
+		tl.Register("", actor)
+		// assert the type of TestActor
+		_, ok := tl.TypeByName("TestActor")
+		assert.False(t, ok)
+		_, ok = tl.Type(actor)
+		assert.True(t, ok)
+	})
+}