Skip to content

Refactor EventFactory into static methods. #311

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 11 commits into from
Jun 20, 2019

Conversation

mikecdavis
Copy link
Contributor

Summary

  • Remove EventFactory from Optimizely constructor.
  • Remove ClientVersion and ClientEngine from OptimizelyBuilder
  • Adds ClientEngineInfo for holding global engine value.
  • Defines new UserEvent, ConversionEvent and ImpressionEvent models
  • Redefines EventFactory to accept UserEvents via static methods
  • Defines UserEventFactory to create UserEvents.

This change is in preparation for batching event payloads so we first want to extract separate canonical representations of conversions and impressions disjoint from the EventBatch model used for sending to the logging backend.

This change is in preparation for an EventProcessor abstraction. #310 is blocked on this change for a cleaner implementation.

@coveralls
Copy link

coveralls commented Jun 14, 2019

Pull Request Test Coverage Report for Build 1100

  • 184 of 194 (94.85%) changed or added relevant lines in 10 files are covered.
  • 3 unchanged lines in 2 files lost coverage.
  • Overall coverage decreased (-0.03%) to 89.719%

Changes Missing Coverage Covered Lines Changed/Added Lines %
core-api/src/main/java/com/optimizely/ab/event/internal/UserEventFactory.java 27 28 96.43%
core-api/src/main/java/com/optimizely/ab/event/internal/ClientEngineInfo.java 8 10 80.0%
core-api/src/main/java/com/optimizely/ab/Optimizely.java 15 18 83.33%
core-api/src/main/java/com/optimizely/ab/event/internal/EventFactory.java 52 56 92.86%
Files with Coverage Reduction New Missed Lines %
core-api/src/main/java/com/optimizely/ab/event/internal/EventFactory.java 1 95.1%
core-api/src/main/java/com/optimizely/ab/Optimizely.java 2 93.24%
Totals Coverage Status
Change from base Build 1094: -0.03%
Covered Lines: 3412
Relevant Lines: 3803

💛 - Coveralls

@mikecdavis mikecdavis changed the title Mikecdaivs/oasis 4895 event factory refact Refactor EventFactory into static methods. Jun 14, 2019
@@ -309,22 +301,18 @@ public void track(@Nonnull String eventName,
logger.warn("Event tags is null when non-null was expected. Defaulting to an empty event tags map.");
}

// create the conversion event request parameters, then dispatch
LogEvent conversionEvent = eventFactory.createConversionEvent(
UserEvent event = UserEventFactory.createConversionEvent(
projectConfig,
userId,
eventType.getId(),
eventType.getKey(),
copiedAttributes,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

might consider moving the copying of attribute into the factory method -- i never understood why it was here

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Those attributes get passed around to DecisionService and to Notifications. I think a refactor is a good idea, but will do so in a follow-up PR.

public class ClientEngineInfo {
private static final Logger logger = LoggerFactory.getLogger(ClientEngineInfo.class);

public static final EventBatch.ClientEngine DEFAULT = EventBatch.ClientEngine.JAVA_SDK;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

side-notes:

  • it would be nice to move that enum out of EventBatch eventually
  • it would be nice to if we set this automatically -- there are some fairly well-known ways to test whether current runtime is Android. ultimately, this shouldn't be user-defined.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah I think you can do that for android in general, but then Mobile vs TV is a little more tricky and we do that already here. This is a step into making it less clunky and having to pass that info everywhere.

this.eventKey = eventKey;
this.revenue = revenue;
this.value = value;
this.tags = tags;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should we default to Collections.emptyMap()?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is enforced in the Optimizely#track call.


private ConversionEvent(String eventId, String eventKey, Number revenue, Number value, Map<String, ?> tags) {
this.eventId = eventId;
this.eventKey = eventKey;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is there a case where it is helpful to allow eventId == null && eventKey == null aside from having an empty constructor?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is enforced in the Optimizely#track call.

projectConfig,
userId,
eventType.getId(),
eventType.getKey(),
copiedAttributes,
eventTags);

// create the conversion event request parameters, then dispatch
LogEvent conversionEvent = EventFactory.createLogEvent(Collections.singletonList(event));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

looks like there's a signature included in this PR that would support EventFactory.createLogEvent(event);

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ha yeah, I didn't go back and update all these :)

this.clientEngine = clientEngine;
@Deprecated
public Builder withClientEngine(EventBatch.ClientEngine clientEngine) {
logger.info("Explicitly setting the ClientEngine is no longer supported.");
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can we temporarily bridge to the new ClientEngineInfo class until future release to avoid silent breakage?

also, this should mention what to use instead and, ideally, the version when it was marked as deprecated

this.tags = tags;
return this;
}

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: convenience method for setting single tag would be nice

public Builder withTag(String tag, Object value) {
    this.tags = (this.tags != null) ? this.tags : new HashMap<>();
    this.tags.put(tag, value);
    return this;
}

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This would probably just be used in testing, but I'll review.


import static com.optimizely.ab.internal.AttributesUtil.isValidNumber;

public class EventFactory {
private static final Logger logger = LoggerFactory.getLogger(EventFactory.class);
static final String EVENT_ENDPOINT = "https://logx.optimizely.com/v1/events"; // Should be part of the datafile
static final String ACTIVATE_EVENT_KEY = "campaign_activated";
public static final String EVENT_ENDPOINT = "https://logx.optimizely.com/v1/events"; // Should be part of the datafile
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

side-note: I was hoping to make this DEFAULT_EVENT_ENDPOINT and add a LogEventFactory that parametrizes this as well as the other constants we are passing to new LogEvent() constructor. I expect that ability to customize the URL will be helpful/necessary in future.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a valid point, but I want to address it when that feature is supported.

public EventFactory(EventBatch.ClientEngine clientEngine, String clientVersion) {
this.clientEngine = clientEngine;
this.clientVersion = clientVersion;
public static LogEvent createLogEvent(List<UserEvent> userEvents) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

worth documenting this functionality, specifically around returning null

.setTags(eventTags)
.setType(eventType.getKey())
.setValue(EventTagUtils.getNumericValue(eventTags))
public static Visitor createConversionEvent(UserEvent userEvent) {
Copy link
Contributor

@loganlinn loganlinn Jun 17, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this should be private (like createImpressionEvent above), no?

@mikecdavis
Copy link
Contributor Author

After SDK CoP and discussion with @loganlinn I'm going to re-work the UserEvent model to remove the "union" behavior it's serving today and instead define a UserContext encapsulating the top-level, shared attributes and have a ConversionEvent and ImpressionEvent take a UserContext in their respective builders.

@mikecdavis mikecdavis requested a review from loganlinn June 18, 2019 22:26
/**
* UserContext stores the user and project context timestamp and global id.
*
* Alt name EventContext?
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd prefer EventContext. Hard for me to associate UUID and timestamp with a userId.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can still have UserContext which holds userId and attributes (and perhaps more things in the future) and then user UserContext in EventContext.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

eh, I can split this out into UserContext and EventContext


if (logger.isDebugEnabled()) {
logger.debug(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I personally found this message useful. Any plans to restore this or moving this to AsyncEventHandler?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's a good idea i can move it there.

@@ -309,22 +301,18 @@ public void track(@Nonnull String eventName,
logger.warn("Event tags is null when non-null was expected. Defaulting to an empty event tags map.");
}

// create the conversion event request parameters, then dispatch
LogEvent conversionEvent = eventFactory.createConversionEvent(
UserEvent event = UserEventFactory.createConversionEvent(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit. May be call this userEvent to be consistent with how you named it above.

this.clientEngine = clientEngine;
@Deprecated
public Builder withClientEngine(EventBatch.ClientEngine clientEngine) {
logger.info("Deprecated. Setting ClientEngine is now set via ClientEngineInfo.");
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit. Sounds incorrect. Perhaps drop the word Setting.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated to "Deprecated. In the future, set ClientEngine via ClientEngineInfo#setClientEngine."

public static void setClientEngine(EventBatch.ClientEngine clientEngine) {
if (clientEngine == null) {
logger.warn("ClientEngine cannot be null, defaulting to {}", ClientEngineInfo.clientEngine.getClientEngineValue());
return;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Where is the default set?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Default is set above. Initial default is JAVA_SDK, but can get updated at any time to another valid option.

Copy link
Contributor

@aliabbasrizvi aliabbasrizvi left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good.

/**
* UserContext stores the user and project context timestamp and global id.
*
* Alt name EventContext?
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit. This comment may be out of date now?

@mikecdavis mikecdavis merged commit bdb3b4e into master Jun 20, 2019
@mikecdavis mikecdavis deleted the mikecdaivs/OASIS-4895-event-factory-refact branch June 20, 2019 19:06
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