Skip to content

Conversation

@whatyouhide
Copy link
Collaborator

The idea here is to push people to use Google.Protobuf.Any directly more. The default type_url can be written down manually, and it's probably going to be a lot more accurate.

I'm thinking of removing Protobuf.Any.type_url_to_module/1 too and make it a private function in this library instead of part of the public API. Thoughts @ericmj?

@whatyouhide whatyouhide requested a review from ericmj December 14, 2022 14:22
@whatyouhide whatyouhide merged commit cf9f2a6 into main Dec 19, 2022
@whatyouhide whatyouhide deleted the andrea/reduce-any-api-surface branch December 19, 2022 16:11
@hassox hassox mentioned this pull request Oct 31, 2023
@yordis
Copy link
Contributor

yordis commented Apr 24, 2025

Could the fully_qualified_name rollback? This is causing some headaches, manual mapping is a lot of work compared to what other ecosystem

@whatyouhide
Copy link
Collaborator Author

@yordis can you expand on the kind of manual mapping you have to do and why it's a lot of work/a headache?

@yordis
Copy link
Contributor

yordis commented Apr 28, 2025

We need to extract the Full Name in order to use the value as the event type in the event store events table, and in the message bus we would use google.protobuf.Any.

Without "Full Name" function, this requires a lot of manual entry of information (unless I am doing something wrong, I am all ears), while in other programming languages that is taken care of (we are polyglot btw, Elixir, Go, TS at the moment).

Just one subdomain alone has 11 events, and 5 commands;

syntax = "proto3";
package messagebus.v1;

import "google/protobuf/any.proto";
import "google/protobuf/timestamp.proto";

message Command {
  string id = 1;
  google.protobuf.Any data = 3;
  map<string, string> metadata = 4;
  google.protobuf.Timestamp created_at = 5;
  string correlation_id = 6;
  string causation_id = 7;
}
syntax = "proto3";
package myapp.v1;

message MyCommand {
  string command_id = 1;
}

message MyEventName {
  string event_id = 1;
}

message Event {
  oneof event {
    MyEventName my_event_name = 1;
  }
}
func eventTypeProvider(event *eventv1.Event) (*string, error) {
	switch e := event.Event.(type) {
	case *eventv1.Event_MyEventName:
                // myapp.v1.MyEventName
		return MessageFullName(e.MyEventName)
	default:
		return nil, trogon.ErrUnknownEvent
	}
}


func MessageFullName[Message protoreflect.ProtoMessage](msg Message) string {
	return msg.ProtoReflect().Descriptor().FullName()
}
func TestExample(t *testing.T) {
	anymsg, err := anypb.New(&myappv1.MyCommand{
		CommandId: "123",
	})
	require.NoError(t, err)

	message := messagebus.CommandMessage{Data: anymsg}

	marshal, err := protojson.Marshal(&message)
	require.NoError(t, err)
	fmt.Sprintf("%v", string(marshal))
        // {"data":{"@type":"type.googleapis.com/myapp.v1.MyCommand", "commandId":"123"}}

	eventype, err := eventTypeProvider(&myappv1.Event{
		Event: &myappv1.Event_MyEventName{
			MyEventName: &myappv1.MyEventName{
				EventId: "123",
			},
		},
	})
	require.NoError(t, err)
	fmt.Sprintf("%v", eventype)
        // myapp.v1.MyEventName
}

func eventTypeProvider(event *myappv1.Event) (string, error) {
	switch e := event.Event.(type) {
	case *myappv1.Event_MyEventName:
		return MessageFullName(e.MyEventName), nil
	default:
		return "", ErrUnknownEvent
	}
}

func MessageFullName[Message protoreflect.ProtoMessage](msg Message) string {
	return string(msg.ProtoReflect().Descriptor().FullName())
}

https://pkg.go.dev/google.golang.org/protobuf/types/known/anypb#MarshalFrom

// MarshalFrom marshals src into dst as the underlying message
// using the provided marshal options.
//
// If no options are specified, call dst.MarshalFrom instead.
func MarshalFrom(dst *Any, src proto.Message, opts proto.MarshalOptions) error {
	const urlPrefix = "type.googleapis.com/"
	if src == nil {
		return protoimpl.X.NewError("invalid nil source message")
	}
	b, err := opts.Marshal(src)
	if err != nil {
		return err
	}
	dst.TypeUrl = urlPrefix + string(src.ProtoReflect().Descriptor().FullName())
	dst.Value = b
	return nil
}

Usage in,

  • Phoenix PubSub (JSON format)
  • Event Store Events (JSON format)
  • Message Bus (JSON format)
  • gRPC (Protobuffer format)
  • JSON API (JSON format)

@whatyouhide
Copy link
Collaborator Author

Is full_name/0 able to predictably return, 100% of the time, the correct full name? I completely lost context on this change, so I’m trying to understand why in the PR description I say constructing URLs manually would be "a lot more accurate".

@yordis
Copy link
Contributor

yordis commented Apr 29, 2025

Is full_name/0 able to predictably return, 100% of the time, the correct full name?

That is what other SDKs are doing, and everything works around such value when dealing with Any, so it is expected to be deterministic value.

"a lot more accurate".

It confused me as well, I was trying to find information about what was the precise problem as well to make sure I understood the problem, I am coming from the perspective that other SDKs do not have such issue, as far as I can tell

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants