class LightCluster implements Cluster {
    private static Logger logger = LoggerFactory.getLogger(LightCluster.class);
    private static Registry registry = SingletonServiceFactory.getBean(Registry.class);
    private static LoadBalance loadBalance = SingletonServiceFactory.getBean(LoadBalance.class);

    public LightCluster() {
        if(logger.isInfoEnabled()) logger.info("A LightCluster instance is started");
    }

    /**
     * Implement serviceToUrl with client side service discovery.
     *
     * @param protocol either http or https
     * @param serviceId unique service identifier - cannot be blank
     * @param requestKey String
     * @return Url discovered after the load balancing. Return null if the corresponding service cannot be found
     */
    @Override
    public String serviceToUrl(String protocol, String serviceId, String tag, String requestKey) {<FILL_FUNCTION_BODY>}

    /**
     *
     * @param protocol either http or https
     * @param serviceId unique service identifier - cannot be blank
     * @param tag an environment tag use along with serviceId for discovery
     * @return List of URI objects
     */
    @Override
    public List<URI> services(String protocol, String serviceId, String tag) {
        if(StringUtils.isBlank(serviceId)) {
            logger.debug("The serviceId cannot be blank");
            return new ArrayList<>();
        }

        // transform to a list of URIs
        return discovery(protocol, serviceId, tag).stream()
                .map(this::toUri)
                .collect(Collectors.toList());
    }

    private List<URL> discovery(String protocol, String serviceId, String tag) {
        if(logger.isDebugEnabled()) logger.debug("protocol = " + protocol + " serviceId = " + serviceId + " tag = " + tag);
        URL subscribeUrl = URLImpl.valueOf(protocol + "://localhost/" + serviceId);
        if(tag != null) {
            subscribeUrl.addParameter(Constants.TAG_ENVIRONMENT, tag);
        }
        if(logger.isDebugEnabled()) logger.debug("subscribeUrl = " + subscribeUrl);
        // subscribe is async and the result won't come back immediately.
        registry.subscribe(subscribeUrl, null);
        // do a lookup for the quick response from either cache or registry service.
        List<URL> urls = registry.discover(subscribeUrl);
        if(logger.isDebugEnabled()) logger.debug("discovered urls = " + urls);
        return urls;
    }

    private URI toUri(URL url) {
        URI uri = null;
        try {
            uri = new URI(url.getProtocol(), null, url.getHost(), url.getPort(), null, null, null);
        } catch (URISyntaxException e) {
            logger.error("URISyntaxExcpetion", e);
        }
        return uri;
    }
}

        if(StringUtils.isBlank(serviceId)) {
            logger.debug("The serviceId cannot be blank");
            return null;
        }
        URL url = loadBalance.select(discovery(protocol, serviceId, tag), serviceId, tag, requestKey);
        if (url != null) {
            logger.debug("Final url after load balance = {}.", url);
            // construct a url in string
            return protocol + "://" + url.getHost() + ":" + url.getPort();
        } else {
            logger.debug("The service: {} cannot be found from service discovery.", serviceId);
            return null;
        }
    
<no_super_class>