0

When dealing with something like analytics I typically have an object that listens for Notifications or calls methods on the object within business logic. This has always bugged me but I haven't been able to find a better pattern. For example, the code below has analytics scattered all over the place and clutters the business logic.

class SomeService {

    private let analytics: AnalyticsGateway

    func doAllTheThings() {
        analytics.trackStart(with: context)

        somethingApiClient.doSomething { error
            guard error = nil else {
                analytics.trackError(error)
                analytics.trackFailure("doSomething", with: context)
            }

            analytics.trackSuccess("doSomething", with: context)
        }

        do {
            try someOtherApiClient.doSomethingElse { [unowned self] mappedObject
                guard let mappedObject = mappedObject else {
                    analytics.trackFailure("doSomethingElse", with: context)
                    return
                }

                guard validator.validate(mappedObject) else {
                    analytics.trackFailure("doSomethingElse", with: context)
                    return
                }

                self.notificationCenter.post(name: SomethingElseDidUpdateNotification,
                                             object: self,
                                             userInfo: info)
            }
        } catch {
            analytics.trackError(error)
            analytics.trackFailure("doSomethingElse", with: context)
        }
    }
}

What is a better approach to dealing with this kind of problem? I like the approach of listening to notifications/events, if they exist to update state throughout the app. However, adding notifications just for analytics seems almost as bad as calling into the analytics object directly (though still better since there's no coupling). There's still analytics-specific code scattered throughout your business logic.

I have done a little bit of aspect-oriented programming with Java in the past and it seems to solve some of this. Is there a way to do something similar in swift? I have found a couple of Objective-C AOP libraries, but neither appear to be maintained.

My current project uses RxSwift and I can do some of this with the events (onNext, onError, etc.)... when I'm using Observables. I'd prefer a better pattern that can be used everywhere, though. Example:

class SomeService {
    func doSomethingElse() -> Observable<SomeObject> {
        return Observable.create { observer in
            someOtherApiClient.doSomethingElse { mappedObject
                guard let mappedObject = mappedObject else {
                    throw ServiceError.mappedObjectIsNil
                }

                guard validator.validate(mappedObject) else {
                    throw ServiceError.validationFailed(mappedObject)
                }

                observer.onNext(mappedObject)
                observer.onCompleted()
            }
        }
        .instrument(for: "doSomethingElse", in: observable)
    }
}

extension Observable where E == Mappable {
    func instrument(for name: String, in observable: Observable<E>) -> Observable<E> {
        return observable.do(onError: { (error) in
            analytics.trackError(error)
            analytics.trackFailure(name, with: context)

        }, onCompleted: {
            analytics.trackSuccess(name, with: context)

        }, onSubscribed: {
            analytics.trackStart(name, with: context)
        })
    }
}
ravun
  • 1,523
  • 9
  • 27
  • 45
  • This is what I typically do: I create a protocol that defines my service's methods. I create an implementation for that protocol. I then create a "Auditor" implementation, which conforms to that protocol by doing all the logging/auditing/w.e., and then calling the respective method on the real implementation. This same technique works for caching, too. – Alexander Jun 30 '17 at 20:25
  • The above is the "decorator" pattern. – Jimbo Feb 27 '18 at 16:56

0 Answers0