Что такое АОП? Основы аспектно-ориентированного программирования
Hello, guys! Без понимания основных концепций довольно сложно вникнуть во фреймворки и подходы к построению функционала. Так что сегодня поговорим об одной из таких концепций — АОП, или аспектно-ориентированное программирование .Это тема не из легких и нечасто применяется напрямую, но во многих фреймворках и технологиях она используется под капотом. Ну и конечно, иногда на собеседованиях вас могут попросить рассказать в общих чертах, что это за зверь такой и где его можно применить. Поэтому давайте рассмотрим основные концепции и несколько несложных примеров AOП на Java .Итак, АОП — аспектно-ориентированное программирование — это парадигма, направленная на повышение модульности различных частей приложения за счет разделения сквозных задач. Для этого к уже существующему коду добавляется дополнительного поведение, без изменений в изначальном коде. Иными словами, мы как бы навешиваем сверху на методы и классы дополнительную функциональность, не внося поправки в модифицируемый код. Зачем это нужно? Рано или поздно мы приходим к тому, что обычный объектно-ориентированный подход не всегда может эффективно решить те или иные задачи. В такой момент на помощь приходит АОП и дает нам дополнительные инструменты для постройки приложения. А дополнительные инструменты — это увеличение гибкости при разработке, благодаря которой появляется больше вариантов решения той или иной задачи.
Применение АОП
Аспектно-ориентированное программирование предназначено для решения сквозных задач, которые могут представлять собой любой код, многократно повторяющийся разными методами, который нельзя полностью структурировать в отдельный модуль. Соответственно, с помощью АОП мы можем оставить это за пределами основного кода и определить его по вертикали. В качестве примера можно привести применение политики безопасности в каком-либо приложении. Как правило безопасность проходит сквозь многие элементы приложения. Тем более, политика безопасности приложения должна применяться одинаково ко всем существующим и новым частям приложения. При этом используемая политика безопасности может и сама развиваться. Вот тут нам отлично может пригодится использование АОП . Также в качестве еще одного примера можно привести логирование. У использования АОП подхода к логированию есть несколько преимуществ по сравнению с ручной вставкой логирования:
Код для логирования легко внедрять и удалять: всего-то нужно добавить или удалить пару конфигураций некоторого аспекта.
Весь исходный код для логирования хранится в одном месте и не нужно находить вручную все места использования.
Код, предназначенный для логирования, можно добавить в любое место, будь то уже написанные методы и классы или же новый функционал. Это уменьшает количество ошибок разработчика. Также при удалении аспекта из конфигурации конструкции можно быть абсолютно уверенным, что весь код трассировки удален и ничего не пропущено.
Аспекты — это вынесенный отдельно код, который можно многократно переиспользовать и улучшать.
Также АОП используется для обработки исключений, кеширования, выноса некоторого функционала, чтобы сделать его переиспользуемым.
Основные понятия АОП
Перед (Before) — советы данного типа запускаются перед выполнением целевых методов — точек соединения. При использовании аспектов в виде классов мы берем @Before аннотацию, чтобы пометить тип совета как идущий перед. При использовании аспектов в виде файлов .aj это будет метод before() .
После (After) — советы, которые выполняются после завершения выполнения методов — точек соединения, как в обычных случаях, так и при бросании исключения. При использовании аспектов в виде классов мы можем использовать @After аннотацию для указания, что это совет, идущий после. При использовании аспектов в виде файлов .aj это будет метод after() .
После возврата (After Returning) — данные советы выполняются только в том случае, когда целевой метод отрабатывает нормально, без ошибок. Когда аспекты представлены в виде классов, мы можем использовать аннотацию @AfterReturning , чтобы пометить совет как выполняемый после успешного завершения. При использовании аспектов в виде файлов .aj это будет метод after() returning (Object obj) .
После бросания (After Throwing) — данный вид советов предназначен для тех случаев, когда метод, то есть точка соединения выдает исключение. Мы можем использовать этот совет для некой обработки неудачного выполнения (к примеру, для отката всей транзакции или логирования с необходимым уровнем трассировки). Для аспектов-классов аннотация @AfterThrowing используется, чтобы указать, что этот совет используется при после броска исключения. При использовании аспектов в виде файлов .aj это будет метод — after() throwing (Exception e) .
Вокруг (Around) — пожалуй, один из самых важных видов советов, который окружает метод, то есть — точку соединения, с помощью которого мы можем, к примеру, выбрать, выполнять данный метод точки соединения или нет. Можно написать код совета, который будет выполняться до и после выполнения метода точки соединения. В обязанности around advice входит вызов метода точки соединения и возвращение значений, если метод что-то возвращает. То есть в этом совете можно попросту сымитировать работу целевого метода, не вызывая его, и в качестве результата вернуть что-то свое. При аспектах в виде классов используем @Around аннотацию для создания советов, оборачивающих точку соединения. При использовании аспектов в виде файлов .aj это будет метод around() .
плетение во время компиляции — если у вас есть исходный код аспекта и код, в котором вы используете аспекты, вы можете скомпилировать исходный код и аспект напрямую с помощью компилятора AspectJ;
посткомпиляционное плетение (бинарное плетение) — если вы не можете или не хотите использовать преобразования исходного кода для вплетения аспектов в код, вы можете взять уже скомпилированные классы или jar-файлы и внедрить аспекты;
плетение во время загрузки — это просто бинарное плетение, отложенное до момента, когда загрузчик классов загрузит файл класса и определит класс для JVM. Для поддержки этого требуется один или несколько «загрузчиков классов плетения». Они либо явно предоставляются средой выполнения, либо активируются с помощью «агента плетения.
Примеры в Java
Далее для большего понимания АОП мы рассмотрим небольшие примеры уровня Hello World.Сразу отмечу, что в наших примерах будем использовать плетение во время компиляции . Сперва нам нужно прописать следующую зависимость в нашем pom.xml :
org.aspectjaspectjrt1.9.5
Как правило для использования аспектов применяют особый компилятор Ajs . В IntelliJ IDEA по умолчанию его нет, поэтому при выборе его как компилятора приложения нужно указать путь к дистрибутиву AspectJ . Подробнее о способе выбора Ajs как компилятора можно почитать на этой странице. Это был первый способ, а второй (которым я и воспользовался) — прописать следующий плагин в pom.xml :
После этого желательно сделать реимпорт у Мавена и запустить mvn clean compile . А теперь перейдём непосредственно к примерам.
Пример №1
Давайте создадим класс Main . В нем у нас будет точка запуска и метод, который печатает в консоли переданные ему имена:
public class Main < public static void main(String[] args) < printName("Толя"); printName("Вова"); printName("Саша"); >public static void printName(String name) < System.out.println(name); >>
Ничего сложного: передали имя — вывели его в консоли. Если мы сейчас запустим, в консоли будет выведено:
Толя Вова Саша
Что ж, пришло время воспользоваться возможностями АОП. Сейчас нам нужно создать файл — аспект . Они бывают двух видов: первый — файл с расширением .aj , второй — обычный класс, который реализует возможности АОП при помощи аннотаций. Давайте сперва рассмотрим файл с расширением .aj :
Данный файл чем-то похож на класс. Разберемся, что здесь происходит: pointcut — срез или набор точек соединения; greeting() — название данного среза; : execution — при выполнении * — всех, вызов — Main.printName(..) — данного метода. Далее идёт конкретный совет — before() — который выполняется до вызова целевого метода, : greeting() — срез, на который данный совет реагирует, ну а ниже мы видим само тело метода, которое написано на понятном нам языке Java. При запуске main с наличием данного аспекта мы получим вывод в консоль:
Привет Толя Привет Вова Привет Саша
Мы видим, что каждый вызов метода printName был модифицирован при помощи аспекта. А теперь давайте взглянем, как будет выглядеть аспект, но уже как класс Java с аннотациями:
@Aspect public class GreetingAspect < @Pointcut("execution(* Main.printName(String))") public void greeting() < >@Before("greeting()") public void beforeAdvice() < System.out.print("Привет "); >>
@Aspect обозначает, что данный класс является аспектом; @Pointcut(«execution(* Main.printName(String))») — точка среза, которая срабатывает на все вызовы Main.printName с входящим аргументом типа String ;
@Before(«greeting()») — совет, который применяется до вызова кода описанного в точке среза greeting() .
Привет Толя Привет Вова Привет Саша
Пример №2
Допустим, у нас есть некоторый метод который осуществляет некоторые операции для клиентов и вызов этого метода из main :
public class Main < public static void main(String[] args) < makeSomeOperation("Толя"); >public static void makeSomeOperation(String clientName) < System.out.println("Выполнение некоторых операций для клиента - " + clientName); >>
С помощью аннотации @Around сделаем что-то типа “псевдотранзакции”:
@Aspect public class TransactionAspect < @Pointcut("execution(* Main.makeSomeOperation(String))") public void executeOperation() < >@Around(value = "executeOperation()") public void beforeAdvice(ProceedingJoinPoint joinPoint) < System.out.println("Открытие транзакции. "); try < joinPoint.proceed(); System.out.println("Закрытие транзакции. "); >catch (Throwable throwable) < System.out.println("Операция не удалась, откат транзакции. "); >> >
С помощью метода proceed объекта ProceedingJoinPoint мы вызываем оборачиваемый метод, чтобы определить его место в совете и, соответственно, код в методе, который выше joinPoint.proceed(); — это Before , который ниже — After . Если мы запустим main , в консоли мы получим:
Открытие транзакции. Выполнение некоторых операций для клиента — Толя Закрытие транзакции.
Если же мы добавим бросок исключения в наш метод (вдруг выполнение операции дало сбой):
public static void makeSomeOperation(String clientName)throws Exception
То мы получим вывод в консоли:
Открытие транзакции. Выполнение некоторых операций для клиента — Толя Операция не удалась, откат транзакции.
Получилась такая себе псевдообработка неудачи.
Пример №3
В качестве следующего примера сделаем что-то типа логирования в консоли. Для начала посмотрим в Main , где у нас происходит псевдо бизнес-логика:
public class Main < private String value; public static void main(String[] args) throws Exception < Main main = new Main(); main.setValue(""); String valueForCheck = main.getValue(); main.checkValue(valueForCheck); > public void setValue(String value) < this.value = value; >public String getValue() < return this.value; >public void checkValue(String value) throws Exception < if (value.length() >10) < throw new Exception(); >> >
В main с помощью setValue мы зададим значение внутренней переменной — value , далее с помощью getValue возьмём это значение и в checkValue проверим, длиннее ли это значение 10 символов. Если да, будет брошено исключение. Теперь посмотрим на аспект, с помощью которого мы будем логировать работу методов:
@Aspect public class LogAspect < @Pointcut("execution(* *(..))") public void methodExecuting() < >@AfterReturning(value = "methodExecuting()", returning = "returningValue") public void recordSuccessfulExecution(JoinPoint joinPoint, Object returningValue) < if (returningValue != null) < System.out.printf("Успешно выполнен метод - %s, класса- %s, с результатом выполнения - %s\n", joinPoint.getSignature().getName(), joinPoint.getSourceLocation().getWithinType().getName(), returningValue); >else < System.out.printf("Успешно выполнен метод - %s, класса- %s\n", joinPoint.getSignature().getName(), joinPoint.getSourceLocation().getWithinType().getName()); >> @AfterThrowing(value = "methodExecuting()", throwing = "exception") public void recordFailedExecution(JoinPoint joinPoint, Exception exception) < System.out.printf("Метод - %s, класса- %s, был аварийно завершен с исключением - %s\n", joinPoint.getSignature().getName(), joinPoint.getSourceLocation().getWithinType().getName(), exception); >>
Когда у метода есть возвращаемое значение if (returningValue != null)
Когда возвращаемого значения нет else
Успешно выполнен метод — setValue, класса- Main Успешно выполнен метод — getValue, класса- Main, с результатом выполнения — <некоторое значение>Метод — checkValue, класса- Main, был аварийно завершен с исключением — java.lang.Exception Метод — main, класса- Main, был аварийно завершен с исключением — java.lang.Exceptionнекоторое>
Ну и так как мы не обработали исключения, еще получим его стектрейс:Почитать об исключениях и их обработке можно в этих статьях: Исключения в Java и Исключения и их обработка. На этом у меня сегодня всё. Сегодня мы познакомились с АОП , и вы смогли увидеть, что сей зверь не так страшен, как его рисуют. Goodbye everyone!
Знакомство с АОП
В современном мире IT-разработки существует довольно большое множество различных подходов к написанию программ. Так, например, кому-то нравиться представлять программу в виде последовательности действий, а кто-то считает, что программа должна представлять собой множество объектов, общающихся друг с другом. Совокупности этих идей и понятий образуют своего рода стиль написания программы, который принято назвать – парадигма программирования.
объект (объектно-ориентированное программирование, С++/Java),
факт (логическое программирование, PROLOG).
В этой статье я хочу рассказать о сравнительно молодой, но крайне, на мой взгляд, полезной парадигме программирования – аспектно-ориентированном программировании.
Основы АОП
Рассмотри некоторую сферическую службу в вакууме (например, web-сервис), реализующую следующий метод:
public BookDTO getBook(Integer bookId) BookDTO book = bookDAO.readBook(bookId); return book; >
Метод довольно прост и очевиден: чтение информации о некоторой книге по её идентификатору. Но давайте подумаем, чего тут не хватает? Первым делом нам стоит задуматься о логировании – без него, как вы сами понимаете, в web-службе никуда:
public BookDTO getBook(Integer bookId) LOG.debug( «Call method getBook with id » + bookId);
BookDTO book = bookDAO.readBook(bookId);
LOG.debug( «Book info is: » + book.toString()); return book; >
Далее необходимо реализовать обработку исключений (сделать так, что бы слой служб возвращал соответствующие ему исключения, скрывая исключения нижележащих слоёв):
public BookDTO getBook(Integer bookId) throws ServiceException LOG.debug( «Call method getBook with id » + bookId); BookDTO book = null ;
try book = bookDAO.readBook(bookId); > catch(SQLException e) throw new ServiceException(e); >
LOG.debug( «Book info is: » + book.toString()); return book; >
Так же не стоит забывать о проверке прав доступа:
public BookDTO getBook(Integer bookId) throws ServiceException, AuthExceptionif (!SecurityContext.getUser().hasRight(«GetBook»)) throw new AuthException(«Permission Denied»);
LOG.debug( «Call method getBook with id » + bookId); BookDTO book = null ;
try book = bookDAO.readBook(bookId); > catch (SQLException e) throw new ServiceException(e); >
LOG.debug( «Book info is: » + book.toString()); return book; >
Кроме того имеет смысл кешировать результат работы:
public BookDTO getBook(Integer bookId) throws ServiceException, AuthException if (!SecurityContext.getUser().hasRight( «GetBook» )) throw new AuthException( «Permission Denied» );
LOG.debug( «Call method getBook with id » + bookId); BookDTO book = null ; String cacheKey = «getBook:» + bookId;
try if (cache.contains(cacheKey)) book = (BookDTO) cache.get(cacheKey); > else book = bookDAO.readBook(bookId); cache.put(cacheKey, book); > > catch (SQLException e) throw new ServiceException(e); >
LOG.debug( «Book info is: » + book.toString()); return book; >
Можно продолжать совершенствовать данный метод, но для начала — достаточно. В ходе наших доработок мы получили метод в 10 раз (с 2 до 20 LOC) превышающий исходный размер. Самое интересное, что объём бизнес-логики в нём не изменился – это всё та же 1 строка. Остальной код реализует некоторую общую служебную функциональность приложения: логирование, обработку ошибок, проверку прав доступа, кеширование и так далее.
логирование,
обработка транзакций,
обработка ошибок,
авторизация и проверка прав,
кэширование,
элементы контрактного программирования.
аспект (aspect) – модуль или класс, реализующий сквозную функциональность. Аспект изменяет поведение остального кода, применяя совет в точках соединения, определённых некоторым срезом. Так же аспект может использоваться для внедрения функциональности;
совет (advice) – дополнительная логика — код, который должен быть вызван из точки соединения. Совет может быть выполнен до, после или вместо точки соединения;
точка соединения (join point) — точка в выполняемой программе (вызов метода, создание объекта, обращение к переменной), где следует применить совет ;
срез (pointcut) — набор точек соединения. Срез определяет, подходит ли данная точка соединения к заданному совету;
внедрение (introduction) — изменение структуры класса и/или изменение иерархии наследования для добавления функциональности аспекта в инородный код;
цель (target) – объект, к которому будут применяться советы;
переплетение (weaving) – связывание объектов с соответствующими аспектами (возможно на этапе компиляции, загрузки или выполнения программы).
Пример использования (AspectJ)
AspectJ является аспектно-ориентированным расширением/framework’ом для языка Java. На данный момент это, пожалуй, самый популярный и развивающийся АОП движок.
Рассмотрим реализацию аспекта логирования с его помощью:
@Aspect public class WebServiceLogger private final static Logger LOG = Logger.getLogger(WebServiceLogger. class );
@Pointcut( «execution(* example.WebService.*(..))» ) public void webServiceMethod()
@Pointcut( «@annotation(example.Loggable)» ) public void loggableMethod()
Первым делом создаётся аспект логирования методов сервисов – класс WebServiceLogger, помеченный аннотацией Aspect. Далее определяются два среза точек соединения: webServiceMethod (вызов метода, принадлежащего классу WebService) и loggableMethod (вызов метода, помеченного аннотацией @Loggable). В завершении объявляется совет (метод logWebServiceCall), который выполняется вместо (аннотация Around) точек соединения, удовлетворяющих срезу («webServiceMethod() && loggableMethod()»).
В коде совета происходит получение информации о текущем методе (точке соединения), логирование начала выполнения метода, непосредственный вызов запрошенного метода, логирование и возвращение результата работы.
execution(static * com.xyz..*.*(..)) – выполнение кода любого статического метода в пакете com.xyz;
call(void MyInterface.*(..)) – вызов любого метода, возвращающего void, интерфейса MyInterface;
initialization(MyClass || MyOtherClass) – инициализация класса MyClass или MyOtherClass;
staticinitialization(MyClass+ && !MyClass) – статическая инициализация класса, имя которого начинается на MyClass, но не сам MyClass;
handler(ArrayOutOfBoundsException) – выполнение обработчика исключения ArrayOutOfBoundsException;
get/set(static int MyClass.x) — чтение / запись свойства x класса MyClass;
this/target(MyClass) – выполнение точки соединения, соответствующей объекту типа MyClass;
args(Integer) – выполнение точки соединения, в которой доступен аргумент типа Integer;
if(thisJoinPoint.getKind().equals(«call»)) – совпадает со всеми точками соединения, в которых заданное выражение истинно;
within/withincode(MyClass) — совпадает со всеми точками соединения, встречающимися в коде заданного класса;
cflow/cflowbelow(call(void MyClass.test())) – совпадает со всеми точками соединения, встречающимися в потоке выполнения заданного среза;
@annotation(MyAnnotation) – выполнение точки соединения, цель которой помечена аннотацией @MyAnnotation.
before – запуск совета до выполнения точки соединения,
after returning — запуск совета после нормального выполнения точки соединения,
after throwing — запуск совета после выброса исключения в процессе выполнения точки соединения,
after — запуск совета после любого варианта выполнения точки соединения,
around – запуск совета вместо выполнения точки соединения (выполнение точки соединения может быть вызвано внутри совета).
Для того, что бы использовать аспекты AspectJ их придётся скомпилировать и «вшить» в основные классы с помощью специального компилятора AJC.
Продукт бесплатный. Распространяется под Eclipse License.
Пример использования (PostSharp)
PostSharp является аспектно-ориентированным framework’ом для платформы .NET. Существуют и другие реализации АОП для .NET, однако, судя по сравнениям с сайта PostSharp, лидирующую позицию занимает именно он.
Рассмотрим, как с помощью него описать аспект обработки исключений. Первым делом необходимо создать класс, расширяющий соответствующий аспект:
public class ExceptionDialogAttribute : OnExceptionAspect public override void OnException(MethodExecutionEventArgs eventArgs) string message = eventArgs.Exception.Message; Window window = Window.GetWindow((DependencyObject)eventArgs.Instance); MessageBox.Show(window, message, «Exception» ); eventArgs.FlowBehavior = FlowBehavior.Continue; > >
Строго говоря, аспекты в терминологии PostSharp – это, как мы можем видеть, аспект и совет в терминологии АОП.
Для того, что бы указать срез точек пересечения для данного аспекта необходимо в файл настроек сборки (AssemblyInfo.cs) добавить следующую строку:
Или же явно пометить интересующие вас методы атрибутом ExceptionDialog:
[ExceptionDialog] public BookDTO GetBook(Integer bookId)
Вот собственно и всё: теперь все выброшенные в соответствующих методах исключения будут обрабатываться созданным аспектом.
OnMethodBoundary/OnMethodInvocation – обращение к методу (начало, конец, выход, выход с исключением);
OnFieldAccess – обращение к свойству;
OnException – обработка исключения;
Composition – внедрение кода;
Продукт платный. Есть Community Edition.
От теории к практике
И так, мы только что увидели, как красиво и эффективно можно решить проблему «выноса за скобки» сквозного функционала в вашем приложении. Однако, это всё теория. На практике всё, естественно, немного иначе 🙂
Прежде всего, в обоих случаях для компиляции и «вшивания» (weaving) аспектов придётся использовать специальный компилятор и тащить вместе с проектом дополнительные библиотеки. Вроде бы, это не проблема: компилятор легко скачивается и интегрируется в среду (например, при использовании maven’a задача сведётся всего лишь к добавлению плагина aspectj-maven-plugin), а множество зависимостей – обычное дело, по крайней мере для Java-приложений (решаемая с помощью того же maven’a). Однако, необходимость включения в проект чего-то, что требует отдельной компиляции, да ещё и не имеет широкого распространения, зачастую отпугивает разработчиков, не смотря на все потенциальные плюсы.
В данном случае решением проблемы может стать Spring Framework [1,2]. Данный фреймворк имеет много достоинств, однако в рамках данной статьи нас интересует его AOP-составляющая. Spring Framework реализует ограниченную AOP-функциональность на чистом Java (C#) без использования сторонних библиотек с помощью создания прокси-объектов (JDK Dynamic Proxy, CGLIB). Другими словами в Spring AOP можно использовать только точки соединения типа «выполнение метода». Однако, как показывает практика, данное ограничение не играет значительной роли, так как для решения большинства задач, требуется точки соединения именно этого типа.
Кроме того, Spring Framework поддерживает конфигурирование приложений c помощью @AspectJ аннотаций, а так же интеграцию аспектов скомпилированных непосредственно с помощью AspectJ.
У себя в компании мы используем именно Spring AOP. Учитывая прочие заслуги Spring Framework, на мой взгляд, он является самой доступной и удобной площадкой для работы с AOP, внося значительный вклад в его популяризацию и развитие.
Резюме
Основная цель АОП — выноса «общей» (сквозной) функциональности «за скобки» (модуляризация сквозной функциональности);
Для Java AOP доступен через проект AspectJ, для .NET – через PostSharp;
Наиболее простая и проверенная реализация AOP – Spring AOP.
ЦУЦ — или как свести 15 тысяч товаров от разных поставщиков на коленке
Для начала представлюсь. Меня зовут Евгений, я работаю в компании OpticsTrade, должность IT-разнорабочий. Компания занимается продажей оптических приборов с 1992 года, а последние несколько лет делает упор на ночную оптику и в частности — тепловизионные прицелы и приборы. По мере роста бизнеса и расширения ассортимента, компания столкнулась с проблемой остатков товаров и актуальными ценами. Если в начале моей работы, количество товара на сайте было в районе 3 тысяч, то на текущий момент позиций более 15 тысяч. Обновлять руками такое количество позиций нереально.
Об о всем по порядку
С чем предстоит работать:
Сайт на OpenCart;
Расширение АОП (Автоматическая обработка прайс-листов);
Поставщики — более 20;
Товары — более 15 тысяч.
Начальство любит экспериментировать и проверять гипотезы, но не всегда задумывается о последствиях, связанными с этими экспериментами.
Во время COVID-19, спрос на дорогие товары для охоты сильно упал, компания приняла решение расширить ассортимент и выйти на рынок туризма. Основная причина этого шага — закрытые границы. Отдел маркетинга и анализ рынка предсказывал увеличение внутреннего туризма и спроса на соответствующие товары.
После того, как выбрали новых поставщиков и договорились с ними о сотрудничестве, на мне повисла задача наполнить сайт огромным количеством новых позиций.
Обсудив с коллегами, принял решение спарсить товары с сайтов поставщиков/конкурентов. В этом мне помог АОП, универсальное решение для работы с товарами и ценами в OpenCart.
При поиске донора для парсинга есть одно важное условие — унифицированные характеристики.
Многие конкуренты и поставщики не обращают на это внимание, но для правильной работы фильтров и сравнения товаров, нужно чтобы название характеристики и ее значение было в едином стиле.
Это две разные характеристики и два разных значения. Соответственно будут дубли, а это чревато тем, что какое-то количество товара при выборе первого фильтра пользователь не увидит.
У основных поставщиков были проблемы с характеристиками, у конкурентов внутренние артикулы отличаются от прайсов поставщиков. При парсинге товаров, мой выбор пал на конкурентов и это стало огромной ошибкой в последующем.
В моменте никто и не думал о том, каким образом будут обновляться цены.
Но когда количество товара переросло отметку в 15 тысяч, компания столкнулась с:
Нестабильный курс
Санкции и новые пути поставок
РРЦ
Цены менялись ежедневно. Совместно с отделами продаж и контента, провели мозговой штурм, о том, как быстро свести тысячи артикулов из прайсов поставщиков и изменять цены в автоматическом режиме. Назвали нашу идею — ЦУЦ (Центр управления ценами).
ЦУЦ 1.0
Для того, чтобы быстро свести артикулы и обновлять цены нам понадобится:
Делаем выгрузку всех товаров с помощью АОП.
Добавляем название или H1 товара в KeyCollector и собираем данные из нужной поисковой системы. В нашем случае это Яндекс.
Экспортируем данные ПС, получаем таблицу:
Сталкиваюсь с проблемой нерелевантных страниц. Товары отличающиеся каким-то свойством, но имеющие одинаковое описание, характеристики. ПС может отдавать приоритет одной карточке товара. В итоге мы имеем то, что на два разных запроса получаем одну ссылку. Такие случаи приходилось проверять руками.
80% ссылок после анализа ПС ссылалось на верный товар.
Создал таблицу, где отсортировал товары по сайтам конкурентов и поставщиков, добавил внутренние артикулы, распределил товар по брендам и принялся настраивать АОП.
Ссылка конкурента 1
Ссылка конкурента 2
Запускаем, цены обновились. Ура! Воскликнули все с облегчением. Но, на самом деле, это было только начало.
В данном способе обновления цен были нюансы. Нужно каждый раз проверять файл с ошибками из-за того, что конкуренты не стоят на месте. Многие вносят правки на свои сайты, часто после редактирования ломаются параметры парсинга. Некоторые конкуренты используют js скрипты в своем коде, привязаться к данным становится очень сложно.
Так же, метод обновления через сайты конкурентов/поставщиков, не дал нормально свести артикулы.
В компании пользовались этим способом на протяжении года, пока не решили выйти на рынок маркетплейсов.
ЦУЦ 2.0
Работа с МП подразумевала под собой наличие точной информации по количеству остатков и актуальных цен у поставщиков. Иначе есть высокий риск продать по цене ниже рынка или не доставить заказанный товар из-за его отсутствия.
Причина этого формат работы: МП — Наша компания — Поставщик
Нужно знать точное количество остатков на складе поставщика. Такую информацию поставщики предоставляют только в прайс листах. Но вот незадача, каждый поставщик имеет индивидуальный формат таблицы.
Использовав PHP SimpleXML и SimpleXLSX создаю скрипт, который форматирует прайсы в единый стиль таблицы.
ГОСУДАРСТВЕННОЕ АВТОНОМНОЕ УЧРЕЖДЕНИЕ ГОРОДА МОСКВЫ «МОСКОВСКОЕ АГЕНТСТВО РЕАЛИЗАЦИИ ОБЩЕСТВЕННЫХ ПРОЕКТОВ»
Действующая организация
ОГРН 1057747228703 от 14 июня 2005 г. ИНН/КПП 7718550691 771801001
Дата регистрации 14.06.2005
Все реквизиты (ФНС / ПФР / ФСС / РОССТАТ)
Юридический адрес 107392 , город Москва , Малая Черкизовская ул., д.22 Еще 1 организация по этому адресу
Руководитель Директор Луценко Сергей Витальевич с 4 июля 2017 г.
Среднесписочная численность нет данных Специальный налоговый режим ░░░░░░░░░░
Реестр МСП не входит
Правопредшественник
Подробнее в выписке из ЕГРЮЛ
Основной вид деятельности Государственное регулирование деятельности в области здравоохранения, образования, социально-культурного развития и других социальных услуг, кроме социального обеспечения (84.12) Все виды деятельности (57)
Налоговый орган Инспекция ФНС России № 18 по г.Москве с 14 июня 2005 г.
Коды статистики ОКПО 77510651 ОКАТО 45263594000 ОКТМО 45316000000 ОКФС 13 Собственность субъектов Российской Федерации ОКОГУ 2300231 — культуры ОКОПФ 75201 Государственные автономные учреждения субъектов Российской Федерации
Телефон +7 (░░░) ░░░-░░-░░ +7 (░░░) ░░░-░░-░░
Электронная почта ░░░░░░░░░░@░░░░░░.ru ░░░░@░░░░░░.ru
Сайт ░░░░░░░░░░.ru ░░░░░░.ru
Для просмотра контактов оформите профессиональный доступ Ваша компания? Уточнить данные
По организации доступны исторические сведения (454 изменения).
Следить за организацией
Как это работает и зачем нужно?
Банковские счета
Отсутствует информация о приостановке операций по счетам налоговыми органами
Проверить на сегодня
Обеспечительные меры
Отсутствует информация о наличии обеспечительных мер на имущество
Проверить на сегодня
Банкротство
Проверить наличие сведений о банкротстве организации в Едином федеральном реестре сведений о банкротстве (ЕФРСБ)
Проверить на сегодня
Документы для госрегистрации
Отсутствует информация о поданных заявлениях
Проверить на сегодня
Отчеты и документы
Актуально на 29.10.2023
ГАУ АОП — руководитель: Луценко Сергей Витальевич (ИНН 370200674602). ИНН 7718550691, ОГРН 1057747228703. ОКПО 77510651, зарегистрировано 14.06.2005 по юридическому адресу 107392, город Москва, Малая Черкизовская ул., д.22. Статус: действующая с 14.06.2005. Подробнее >
До Луценко Сергей Витальевич, руководителем «ГАУ АОП» являлись: Бесполденов Александр Владимирович (ИНН 622802450102), Гудыма Георгий Валерьевич (ИНН 772904966356), Игнатова Екатерина Анатольевна (ИНН 773606771709), Давлеткалиев Денис Куанышевич (ИНН 263204175738). Ранее ГАУ АОП находилось по адресу: 107392, город Москва, улица Черкизовская М., 22.
Компания работает 18 лет 4 месяца, с 14 июня 2005 по настоящее время. В выписке ЕГРЮЛ учредителем указана 1 государственная структура. Основной вид деятельности «ГАУ АОП» — Государственное регулирование деятельности в области здравоохранения, образования, социально-культурного развития и других социальных услуг, кроме социального обеспечения и 56 дополнительных видов.
Состоит на учете в налоговом органе Инспекция ФНС России № 18 по г.Москве с 14 июня 2005 г., присвоен КПП 771801001. Регистрационный номер ПФР 087407002348, ФСС 772402386677381. < Свернуть