The ShortCode service replaces the previous resolver and shortcode-create services.

The decision to merge these two services into the unified ShortCode service came about to eliminate cold starts in the shortcode-create service. As there is a high level of traffic to shortcodes, ensuring a copy of any resolver service is usually spun up, it makes sense to use this already running service to also create shortcodes, rather than cold start a separate service.

Service Configuration

This is the service setup for the ShortCode service. It uses my service library which ties together multiple aspects of a modern microservice. The http Router used here is actually Fiber as I recently ported my service library away from my own in-house http router to use Fiber, for the advantage of speed and throughput.

The provision of resources here allows the service to serve incoming http requests, store and retrieve shortcodes from the GCP firestore NoSQL database and send out pub/sub events relating to the creation of shortcodes.

func main() {
	s := service.New("shortCode")
	s.Router().Get("/", handlerRootRedirect.Handle, "redirectFromShortDomain")
	s.Router().Get("/:shortCode", handlerView.Handle, "viewShortcode")
	s.Router().Post("/create", handlerCreate.Handle, "createShortcode")

	if err := s.Config().Populate(&conf.Config); err != nil {
		s.Log().Error("unable to populate config", "error", err.Error())
		os.Exit(0)
	}

	s.WithDb(fireStore.New(conf.Config.GCP.ProjectId, conf.Config.GCP.Credentials))

	sender, err := gcpPubSub.NewSender(conf.Config.GCP.ProjectId, conf.Config.Service.Handlers.Create.PubSub.Topics.Created)
	if err != nil {
		s.Log().Error("unable to create pub/sub sender for topic", "topic", conf.Config.Service.Handlers.Create.PubSub.Topics.Created, 
		    "error", err.Error())
		os.Exit(0)
	}
	sender.Credentials(conf.Config.GCP.Credentials)
	s.WithPubSubTopic(conf.Config.Service.Handlers.Create.PubSub.Topics.Created, sender)
	s.Run()
}

Endpoints

Create

The create endpoint converts long URLs into shortcodes and returns a JWT token to the web UI which contains the shortcode in it’s claims. This endpoint has been optimised for lowest possible request duration in a number of ways

func Handle(bundle serviceComponents.Bundle, c serviceComponents.Request) error {
	logger := bundle.Log().Child("creator ip", c.IP())

	cacheItem := bundle.Cache().Namespace("flow-control").Get("has-capacity")
	doesHaveCapacity, ok := cacheItem.(bool)
	if ok {
		if !doesHaveCapacity {
			return c.Status(http.StatusTooManyRequests).SendString("slow down - rate limited")
		}
	}

	span := bundle.Span("processing create request")
	shortCode, err := translateRequest.Action(c.Body(), c.IP(), logger, span)
	if err != nil {
		span.Error("error processing create request")
		return c.Status(http.StatusBadRequest).SendString("you must supply a valid create request")
	}

	existing, err := existingShortcode.Get(shortCode, bundle, logger, span)
	if err != nil {
		span.Error("error checking if shortCode destination exists already")
		return c.Status(http.StatusFailedDependency).SendString("error verifying shortCode with backend storage")
	}
	span.Success()

	if len(existing.Id) > 0 {
		token, err := getJwtForShortCode.Action(bundle, existing, false)
		if err != nil {
			return c.Status(http.StatusFailedDependency).SendString("error verifying shortCode")
		}

		go func(ip string) {
			postProcessing.Action(shortCode, ip, bundle, logger, false)
		}(c.IP())

		return c.SendString(token)
	}

	span = bundle.Span("setting up shortCode")
	if err = setupShortcode.Action(&shortCode, bundle, span, logger); err != nil {
		span.Error("error setting up shortCode")
		return c.Status(http.StatusFailedDependency).SendString("error setting up shortCode")
	}
	span.Success()

	bundle.Attribute("shortCode", shortCode.ShortCode.URL)

	go func(ip string) {
		postProcessing.Action(shortCode, ip, bundle, logger, true)
	}(c.IP())

	token, err := getJwtForShortCode.Action(bundle, shortCode, false)
	if err != nil {
		return c.Status(http.StatusFailedDependency).SendString("processing error")
	}
	return c.SendString(token)
}